Merge changes I771230c1,I460dfc11 into main
* changes:
Support media on splitshade with constraints
Fix NSSL flicker when going to/from OCCLUDED
diff --git a/Android.bp b/Android.bp
index bb93048..13b1703 100644
--- a/Android.bp
+++ b/Android.bp
@@ -174,6 +174,9 @@
// and remove this line.
"//frameworks/base/tools/hoststubgen:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// AIDL files under these paths are mixture of public and private ones.
@@ -264,6 +267,9 @@
],
sdk_version: "core_platform",
installable: false,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -432,6 +438,9 @@
],
sdk_version: "core_platform",
installable: false,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -475,6 +484,9 @@
],
compile_dex: false,
headers_only: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -502,6 +514,9 @@
"-Xep:AndroidFrameworkUid:ERROR",
],
},
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -516,6 +531,7 @@
},
lint: {
enabled: false,
+ baseline_filename: "lint-baseline.xml",
},
}
@@ -540,6 +556,9 @@
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -555,6 +574,9 @@
"calendar-provider-compat-config",
"contacts-provider-platform-compat-config",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
platform_compat_config {
@@ -609,6 +631,9 @@
"rappor",
],
dxflags: ["--core-library"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// utility classes statically linked into framework-wifi and dynamically linked
@@ -634,6 +659,9 @@
"//frameworks/base/services/net",
"//packages/modules/Wifi/framework",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
filegroup {
diff --git a/core/api/current.txt b/core/api/current.txt
index 43ff0c9..df073dd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12932,6 +12932,7 @@
field public static final String FEATURE_MIDI = "android.software.midi";
field public static final String FEATURE_NFC = "android.hardware.nfc";
field public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+ field @FlaggedApi("android.nfc.enable_nfc_charging") public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
field public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
field public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
field public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
@@ -28813,6 +28814,7 @@
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
@@ -28820,6 +28822,7 @@
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
method public boolean isSecureNfcEnabled();
method public boolean isSecureNfcSupported();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28905,6 +28908,20 @@
ctor public TagLostException(String);
}
+ @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
+ ctor public WlcLDeviceInfo(double, double, double, int);
+ method public int describeContents();
+ method public double getBatteryLevel();
+ method public double getProductId();
+ method public int getState();
+ method public double getTemperature();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTED_CHARGING = 2; // 0x2
+ field public static final int CONNECTED_DISCHARGING = 3; // 0x3
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
+ field public static final int DISCONNECTED = 1; // 0x1
+ }
+
}
package android.nfc.cardemulation {
@@ -45682,6 +45699,7 @@
field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED";
field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED";
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
+ field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE";
field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 51e61e6..0d4169f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3357,7 +3357,7 @@
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
method public int describeContents();
- method @StringRes public int getDisplayNameStringRes();
+ method @NonNull public String getName();
method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
@@ -3367,7 +3367,7 @@
ctor public VirtualCameraConfig.Builder();
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
- method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
@@ -9884,17 +9884,20 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
@@ -9909,6 +9912,10 @@
method public boolean onUnlockAttempted(android.nfc.Tag);
}
+ @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+ method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+ }
+
}
package android.nfc.cardemulation {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6ddb36a..6df0f6b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -407,7 +407,7 @@
private int mLastSessionId;
// Holds the value of the last reported device ID value from the server for the top activity.
- int mLastReportedDeviceId;
+ int mLastReportedDeviceId = Context.DEVICE_ID_DEFAULT;
final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
@UnsupportedAppUsage
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -4856,10 +4856,13 @@
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
if (!service.isUiContext()) { // WindowProviderService is a UI Context.
- VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
- if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
- || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT) {
service.updateDeviceId(mLastReportedDeviceId);
+ } else {
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm != null && vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ service.updateDeviceId(mLastReportedDeviceId);
+ }
}
}
service.onCreate();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b7db5f5..c3adbc3 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -222,7 +222,7 @@
boolean removeAutomaticZenRule(String id, boolean fromUser);
boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
- void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
+ void setAutomaticZenRuleState(String id, in Condition condition);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f76a45b..0b6e24c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1391,20 +1391,9 @@
* @param condition The new state of this rule
*/
public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
- if (Flags.modesApi()) {
- setAutomaticZenRuleState(id, condition,
- /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
- } else {
- setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
- }
- }
-
- /** @hide */
- public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
- boolean fromUser) {
INotificationManager service = getService();
try {
- service.setAutomaticZenRuleState(id, condition, fromUser);
+ service.setAutomaticZenRuleState(id, condition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 7418c06..9f97f6f 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,7 +16,7 @@
package android.app.servertransaction;
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
@@ -67,7 +67,7 @@
* window configuration.
*/
public void onDisplayChanged(int displayId) {
- if (!isSyncWindowConfigUpdateFlagEnabled()) {
+ if (!isBundleClientTransactionFlagEnabled()) {
return;
}
if (ActivityThread.isSystem()) {
@@ -77,9 +77,9 @@
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
- /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */
- public boolean isSyncWindowConfigUpdateFlagEnabled() {
+ /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
+ public boolean isBundleClientTransactionFlagEnabled() {
// Can't read flag from isolated process.
- return !Process.isIsolated() && syncWindowConfigUpdateFlag();
+ return !Process.isIsolated() && bundleClientTransactionFlag();
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 9f5e0dc..ee48e43 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -32,7 +32,7 @@
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import android.annotation.NonNull;
import android.app.ActivityThread.ActivityClientRecord;
@@ -183,9 +183,9 @@
}
// Can't read flag from isolated process.
- final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
- && syncWindowConfigUpdateFlag();
- final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+ final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated()
+ && bundleClientTransactionFlag();
+ final Context configUpdatedContext = isBundleClientTransactionFlagEnabled
? item.getContextToUpdate(mTransactionHandler)
: null;
final Configuration preExecutedConfig = configUpdatedContext != null
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index a939251..59fe9a1 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -20,11 +20,9 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
-import android.content.res.Resources;
import android.graphics.ImageFormat;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,16 +43,16 @@
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public final class VirtualCameraConfig implements Parcelable {
- private final @StringRes int mNameStringRes;
+ private final String mName;
private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
private final IVirtualCameraCallback mCallback;
private VirtualCameraConfig(
- int displayNameStringRes,
+ @NonNull String name,
@NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
@NonNull Executor executor,
@NonNull VirtualCameraCallback callback) {
- mNameStringRes = displayNameStringRes;
+ mName = requireNonNull(name, "Missing name");
mStreamConfigurations =
Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
if (mStreamConfigurations.isEmpty()) {
@@ -68,7 +66,7 @@
}
private VirtualCameraConfig(@NonNull Parcel in) {
- mNameStringRes = in.readInt();
+ mName = in.readString8();
mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
mStreamConfigurations =
Set.of(
@@ -84,18 +82,18 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mNameStringRes);
+ dest.writeString8(mName);
dest.writeStrongInterface(mCallback);
dest.writeParcelableArray(
mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
}
/**
- * @return The display name of this VirtualCamera
+ * @return The name of this VirtualCamera
*/
- @StringRes
- public int getDisplayNameStringRes() {
- return mNameStringRes;
+ @NonNull
+ public String getName() {
+ return mName;
}
/**
@@ -126,30 +124,22 @@
* <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
* <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
* VirtualCameraCallback)}
- * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
+ * <li>A camera name must be set with {@link #setName(String)}
*/
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
- private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+ private String mName;
private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
/**
- * Set the visible name of this camera for the user.
- *
- * <p>Sets the resource to a string representing a user readable name for this virtual
- * camera.
- *
- * @throws IllegalArgumentException if an invalid resource id is passed.
+ * Set the name of the virtual camera instance.
*/
@NonNull
- public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
- if (displayNameStringRes <= 0) {
- throw new IllegalArgumentException("Invalid resource passed for display name");
- }
- mDisplayNameStringRes = displayNameStringRes;
+ public Builder setName(@NonNull String name) {
+ mName = requireNonNull(name, "Display name cannot be null");
return this;
}
@@ -203,7 +193,7 @@
@NonNull
public VirtualCameraConfig build() {
return new VirtualCameraConfig(
- mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
+ mName, mStreamConfigurations, mCallbackExecutor, mCallback);
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a863870..8151a91 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,8 @@
package android.content.pm;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
@@ -55,7 +57,6 @@
import android.content.IntentSender;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -91,6 +92,10 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageInfoCommonUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DataClass;
@@ -817,6 +822,8 @@
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
+ MATCH_DIRECT_BOOT_AWARE,
+ MATCH_DIRECT_BOOT_UNAWARE,
GET_ATTRIBUTIONS_LONG,
})
@Retention(RetentionPolicy.SOURCE)
@@ -2518,6 +2525,7 @@
USER_MIN_ASPECT_RATIO_16_9,
USER_MIN_ASPECT_RATIO_3_2,
USER_MIN_ASPECT_RATIO_FULLSCREEN,
+ USER_MIN_ASPECT_RATIO_APP_DEFAULT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserMinAspectRatio {}
@@ -2571,6 +2579,16 @@
*/
public static final int USER_MIN_ASPECT_RATIO_FULLSCREEN = 6;
+ /**
+ * Aspect ratio override code: user sets to app's default aspect ratio.
+ * This resets both the user-forced aspect ratio, and the device manufacturer
+ * per-app override {@link ActivityInfo#OVERRIDE_ANY_ORIENTATION_TO_USER}.
+ * It is different from {@link #USER_MIN_ASPECT_RATIO_UNSET} as the latter may
+ * apply the device manufacturer per-app orientation override if any,
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_APP_DEFAULT = 7;
+
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
@@ -3295,6 +3313,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports NFC charging.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_NFC_CHARGING)
+ public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The Beam API is enabled on the device.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -3304,7 +3330,7 @@
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports any
* one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION},
- * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features.
+ * {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF}, or {@link #FEATURE_NFC_CHARGING} features.
*
* @hide
*/
@@ -8609,28 +8635,56 @@
@Nullable
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
@NonNull PackageInfoFlags flags) {
- long flagsBits = flags.getValue();
- final PackageParser parser = new PackageParser();
- parser.setCallback(new PackageParser.CallbackImpl(this));
final File apkFile = new File(archiveFilePath);
- try {
- if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
- // Caller expressed an explicit opinion about what encryption
- // aware/unaware components they want to see, so fall through and
- // give them what they want
- } else {
- // Caller expressed no opinion, so match everything
- flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
- }
- PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
- if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
- PackageParser.collectCertificates(pkg, false /* skipVerify */);
- }
- return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
- FrameworkPackageUserState.DEFAULT);
- } catch (PackageParser.PackageParserException e) {
- Log.w(TAG, "Failure to parse package archive", e);
+ @PackageInfoFlagsBits long flagsBits = flags.getValue();
+ if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
+ // Caller expressed an explicit opinion about what encryption
+ // aware/unaware components they want to see, so fall through and
+ // give them what they want
+ } else {
+ // Caller expressed no opinion, so match everything
+ flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
+ int parserFlags = 0;
+ if ((flagsBits & (GET_SIGNATURES | GET_SIGNING_CERTIFICATES)) != 0) {
+ parserFlags |= PARSE_COLLECT_CERTIFICATES;
+ }
+
+ final PackageParser2 parser2 = new PackageParser2(/*separateProcesses*/ null,
+ /*displayMetrics*/ null,/*cacher*/ null,
+ new PackageParser2.Callback() {
+ @Override
+ public boolean hasFeature(String feature) {
+ return PackageManager.this.hasSystemFeature(feature);
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getHiddenApiWhitelistedApps() {
+ return Collections.emptySet();
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getInstallConstraintsAllowlist() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isChangeEnabled(long changeId,
+ @androidx.annotation.NonNull ApplicationInfo appInfo) {
+ return false;
+ }
+ });
+
+ try {
+ ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false);
+
+ return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId());
+ } catch (PackageParserException e) {
+ Log.w(TAG, "Failure to parse package archive apkFile= " +apkFile);
return null;
}
}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9a1796f..c7797c7 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -64,3 +64,10 @@
bug: "296829976"
is_fixed_read_only: true
}
+
+flag {
+ name: "allow_resolver_sheet_for_private_space"
+ namespace: "profile_experiences"
+ description: "Add support for Private Space in resolver sheet"
+ bug: "307515485"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03db..60ad8e8 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@
private static native long nativeCreate(String opPackageName);
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
+ private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+ Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@
// initialize the sensor list
for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
- if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+ if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+ } else {
+ if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ }
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
-
}
/** @hide */
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889d..665d8d2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+ * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+ * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
if (mAvailableSessionCharacteristicsKeys == null) {
- mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+ mAvailableSessionCharacteristicsKeys =
+ Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
}
return mAvailableSessionCharacteristicsKeys;
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212..64a62a9 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@
*/
boolean blockScreenOn(Runnable unblocker);
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(int mode);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index f6beec1..967a0cc 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -30,8 +30,10 @@
import android.nfc.INfcUnlockHandler;
import android.nfc.ITagRemovedCallback;
import android.nfc.INfcDta;
+import android.nfc.INfcWlcStateListener;
import android.nfc.NfcAntennaInfo;
import android.os.Bundle;
+import android.nfc.WlcLDeviceInfo;
/**
* @hide
@@ -86,4 +88,11 @@
boolean enableReaderOption(boolean enable);
boolean isObserveModeSupported();
boolean setObserveMode(boolean enabled);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ boolean enableWlc(boolean enable);
+ boolean isWlcEnabled();
+ void registerWlcStateListener(in INfcWlcStateListener listener);
+ void unregisterWlcStateListener(in INfcWlcStateListener listener);
+ WlcLDeviceInfo getWlcLDeviceInfo();
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java b/core/java/android/nfc/INfcWlcStateListener.aidl
similarity index 62%
rename from packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
rename to core/java/android/nfc/INfcWlcStateListener.aidl
index a979cf8..c2b7075 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
+++ b/core/java/android/nfc/INfcWlcStateListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 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.
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.model.installstagedata;
+package android.nfc;
-public class InstallStaging extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_STAGING;
-
- @Override
- public int getStageCode() {
- return mStage;
- }
+import android.nfc.WlcLDeviceInfo;
+/**
+ * @hide
+ */
+oneway interface INfcWlcStateListener {
+ /**
+ * Called whenever NFC WLC state changes
+ *
+ * @param wlcLDeviceInfo NFC wlc listener information
+ */
+ void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f407fb7..21e23ae 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -75,6 +75,7 @@
static final String TAG = "NFC";
private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+ private final NfcWlcStateListener mNfcWlcStateListener;
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -440,6 +441,7 @@
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
static boolean sHasCeFeature;
+ static boolean sHasNfcWlcFeature;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -650,8 +652,9 @@
|| pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
|| pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
|| pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
+ sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
/* is this device meant to have NFC */
- if (!sHasNfcFeature && !sHasCeFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
Log.v(TAG, "this device does not have NFC support");
throw new UnsupportedOperationException();
}
@@ -776,6 +779,7 @@
mTagRemovedListener = null;
mLock = new Object();
mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+ mNfcWlcStateListener = new NfcWlcStateListener(getService());
}
/**
@@ -944,7 +948,8 @@
Log.e(TAG, "Failed to recover NFC Service.");
}
}
- return serviceState && (isTagReadingEnabled() || isCardEmulationEnabled());
+ return serviceState
+ && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
}
/**
@@ -2587,4 +2592,159 @@
return false;
}
}
+
+ /**
+ * Sets NFC charging feature.
+ * <p>This API is for the Settings application.
+ * @return True if successful
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean enableWlc(boolean enable) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.enableWlc(enable);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.enableWlc(enable);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks NFC charging feature is enabled.
+ *
+ * @return True if NFC charging is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public boolean isWlcEnabled() {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isWlcEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isWlcEnabled();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A listener to be invoked when NFC controller always on state changes.
+ * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+ * NfcAdapter#registerWlcStateListener} and disable it with {@link
+ * NfcAdapter#unregisterWlcStateListenerListener}.
+ * @see #registerWlcStateListener
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public interface WlcStateListener {
+ /**
+ * Called on NFC WLC state changes
+ */
+ void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+ }
+
+ /**
+ * Register a {@link WlcStateListener} to listen for NFC WLC state changes
+ * <p>The provided listener will be invoked by the given {@link Executor}.
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link WlcStateListener}
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public void registerWlcStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WlcStateListener listener) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ mNfcWlcStateListener.register(executor, listener);
+ }
+
+ /**
+ * Unregister the specified {@link WlcStateListener}
+ * <p>The same {@link WlcStateListener} object used when calling
+ * {@link #registerWlcStateListener(Executor, WlcStateListener)}
+ * must be used.
+ *
+ * <p>Listeners are automatically unregistered when application process goes away
+ *
+ * @param listener user implementation of the {@link WlcStateListener}a
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public void unregisterWlcStateListener(
+ @NonNull WlcStateListener listener) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ mNfcWlcStateListener.unregister(listener);
+ }
+
+ /**
+ * Returns information on the NFC charging listener device
+ *
+ * @return Information on the NFC charging listener device
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ @Nullable
+ public WlcLDeviceInfo getWlcLDeviceInfo() {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.getWlcLDeviceInfo();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return null;
+ }
+ try {
+ return sService.getWlcLDeviceInfo();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return null;
+ }
+ }
}
diff --git a/core/java/android/nfc/NfcWlcStateListener.java b/core/java/android/nfc/NfcWlcStateListener.java
new file mode 100644
index 0000000..8d79310
--- /dev/null
+++ b/core/java/android/nfc/NfcWlcStateListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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 android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.WlcStateListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
+ private static final String TAG = NfcWlcStateListener.class.getSimpleName();
+
+ private final INfcAdapter mAdapter;
+
+ private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
+
+ private WlcLDeviceInfo mCurrentState = null;
+ private boolean mIsRegistered = false;
+
+ public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register a {@link WlcStateListener} with this
+ * {@link WlcStateListener}
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link WlcStateListener}
+ */
+ public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
+ synchronized (this) {
+ if (mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.put(listener, executor);
+
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerWlcStateListener(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link WlcStateListener}
+ *
+ * @param listener user implementation of the {@link WlcStateListener}
+ */
+ public void unregister(@NonNull WlcStateListener listener) {
+ synchronized (this) {
+ if (!mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.remove(listener);
+
+ if (mListenerMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterWlcStateListener(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull WlcStateListener listener) {
+ synchronized (this) {
+ Executor executor = mListenerMap.get(listener);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onWlcStateChanged(
+ mCurrentState));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
+ synchronized (this) {
+ mCurrentState = wlcLDeviceInfo;
+
+ for (WlcStateListener cb : mListenerMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/core/java/android/nfc/WlcLDeviceInfo.aidl
similarity index 71%
copy from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
copy to core/java/android/nfc/WlcLDeviceInfo.aidl
index b8a9355..33143fe 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/core/java/android/nfc/WlcLDeviceInfo.aidl
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.ui;
+package android.nfc;
-public interface UninstallActionListener {
-
- void onPositiveResponse(boolean keepData);
-
- void onNegativeResponse();
-}
+parcelable WlcLDeviceInfo;
diff --git a/core/java/android/nfc/WlcLDeviceInfo.java b/core/java/android/nfc/WlcLDeviceInfo.java
new file mode 100644
index 0000000..016431e
--- /dev/null
+++ b/core/java/android/nfc/WlcLDeviceInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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 android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcLDeviceInfo implements Parcelable {
+ public static final int DISCONNECTED = 1;
+
+ public static final int CONNECTED_CHARGING = 2;
+
+ public static final int CONNECTED_DISCHARGING = 3;
+
+ private double mProductId;
+ private double mTemperature;
+ private double mBatteryLevel;
+ private int mState;
+
+ public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
+ this.mProductId = productId;
+ this.mTemperature = temperature;
+ this.mBatteryLevel = batteryLevel;
+ this.mState = state;
+ }
+
+ /**
+ * ProductId of the WLC listener device.
+ */
+ public double getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * Temperature of the WLC listener device.
+ */
+ public double getTemperature() {
+ return mTemperature;
+ }
+
+ /**
+ * BatteryLevel of the WLC listener device.
+ */
+ public double getBatteryLevel() {
+ return mBatteryLevel;
+ }
+
+ /**
+ * State of the WLC listener device.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ private WlcLDeviceInfo(Parcel in) {
+ this.mProductId = in.readDouble();
+ this.mTemperature = in.readDouble();
+ this.mBatteryLevel = in.readDouble();
+ this.mState = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
+ new Parcelable.Creator<WlcLDeviceInfo>() {
+ @Override
+ public WlcLDeviceInfo createFromParcel(Parcel in) {
+ return new WlcLDeviceInfo(in);
+ }
+
+ @Override
+ public WlcLDeviceInfo[] newArray(int size) {
+ return new WlcLDeviceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeDouble(mProductId);
+ dest.writeDouble(mTemperature);
+ dest.writeDouble(mBatteryLevel);
+ dest.writeDouble(mState);
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f9..41dee3a 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
package android.nfc.cardemulation;
import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@
// Set uid
mUid = si.applicationInfo.uid;
- mCategoryOtherServiceEnabled = false; // support other category
+ mCategoryOtherServiceEnabled = true; // support other category
}
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e0427..ce4f777 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,17 @@
description: "Enable NFC Polling Loop Notifications ST shim"
bug: "294217286"
}
+
+flag {
+ name: "enable_tag_detection_broadcasts"
+ namespace: "nfc"
+ description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+ bug: "306203494"
+}
+
+flag {
+ name: "enable_nfc_charging"
+ namespace: "nfc"
+ description: "Flag for NFC charging changes"
+ bug: "292143899"
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523e..80ec458 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -757,7 +757,6 @@
@Nullable String invokeWith,
@Nullable String packageName,
@Nullable long[] disabledCompatChanges,
- boolean bindMountSyspropOverrides,
@Nullable String[] zygoteArgs) {
// Webview zygote can't access app private data files, so doesn't need to know its data
// info.
@@ -767,7 +766,7 @@
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
disabledCompatChanges, /* pkgDataInfoMap */ null,
/* whitelistedDataInfoMap */ null, /* bindMountAppsData */ false,
- /* bindMountAppStorageDirs */ false, bindMountSyspropOverrides, zygoteArgs);
+ /* bindMountAppStorageDirs */ false, /* bindMountSyspropOverrides */ false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 49ce40b..df503e8 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* An exception specific to a service.
@@ -33,6 +34,7 @@
* @hide
*/
@SystemApi
+@RavenwoodKeepWholeClass
public class ServiceSpecificException extends RuntimeException {
public final int errorCode;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 942ce971..54cc5f4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15021,6 +15021,16 @@
"foreground_service_starts_logging_enabled";
/**
+ * Describes whether AM's AppProfiler should collect PSS even if RSS is the default. This
+ * can be set by a user in developer settings.
+ * Default: 0
+ * @hide
+ */
+ @Readable
+ public static final String FORCE_ENABLE_PSS_PROFILING =
+ "force_enable_pss_profiling";
+
+ /**
* @hide
* @see com.android.server.appbinding.AppBindingConstants
*/
@@ -19628,6 +19638,15 @@
*/
public static final String WEAR_POWER_ANOMALY_SERVICE_ENABLED =
"wear_power_anomaly_service_enabled";
+
+ /**
+ * A boolean that tracks whether Wrist Detection Auto-Locking is enabled.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
+ "wear_wrist_detection_auto_locking_enabled";
}
}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3..30524a1 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@
description: "Collect sepolicy hash from sysfs"
bug: "308471499"
}
+
+flag {
+ name: "frp_enforcement"
+ namespace: "android_hw_security"
+ description: "This flag controls whether PDB enforces FRP"
+ bug: "290312729"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ac9ad2d..feccc6b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2320,6 +2320,15 @@
* @hide
*/
public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+ final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
+ synchronized (mH) {
+ if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+ // Fail early if the view is not focused and not served
+ // to avoid logging many erroneous calls.
+ return false;
+ }
+ }
+
final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
null /* component */, Process.myUid(),
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 933cc49..59d7b0e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -2,13 +2,6 @@
# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
-flag {
- namespace: "windowing_sdk"
- name: "sync_window_config_update_flag"
- description: "Whether the feature to sync different window-related config updates is enabled"
- bug: "260873529"
-}
-
# Using a fixed read only flag because there are ClientTransaction scheduling before
# WindowManagerService creation.
flag {
@@ -35,13 +28,6 @@
flag {
namespace: "windowing_sdk"
- name: "window_state_resize_item_flag"
- description: "Whether to dispatch window resize through ClientTransaction is enabled"
- bug: "301870955"
-}
-
-flag {
- namespace: "windowing_sdk"
name: "fullscreen_dim_flag"
description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
bug: "253533308"
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7534d29..7dcbbea 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -249,6 +249,7 @@
private UserHandle mCloneProfileUserHandle;
private UserHandle mTabOwnerUserHandleForLaunch;
+ private UserHandle mPrivateProfileUserHandle;
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
@@ -441,6 +442,7 @@
mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
mWorkProfileUserHandle = fetchWorkProfileUserProfile();
mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+ mPrivateProfileUserHandle = fetchPrivateProfileUserHandle();
mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
// The last argument of createResolverListAdapter is whether to do special handling
@@ -648,7 +650,8 @@
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ getPersonalProfileUserHandle());
+ getPersonalProfileUserHandle());
+
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
@@ -747,6 +750,9 @@
}
protected UserHandle getPersonalProfileUserHandle() {
+ if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
+ return mPrivateProfileUserHandle;
+ }
return mPersonalProfileUserHandle;
}
protected @Nullable UserHandle getWorkProfileUserHandle() {
@@ -761,6 +767,10 @@
return mTabOwnerUserHandleForLaunch;
}
+ protected UserHandle getPrivateProfileUserHandle() {
+ return mPrivateProfileUserHandle;
+ }
+
protected UserHandle fetchPersonalProfileUserHandle() {
// ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
// profile is active, we always make the personal tab from the foreground user.
@@ -795,12 +805,28 @@
return mCloneProfileUserHandle;
}
+ protected @Nullable UserHandle fetchPrivateProfileUserHandle() {
+ mPrivateProfileUserHandle = null;
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo :
+ userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+ if (userInfo.isPrivateProfile()) {
+ mPrivateProfileUserHandle = userInfo.getUserHandle();
+ break;
+ }
+ }
+ return mPrivateProfileUserHandle;
+ }
+
private UserHandle fetchTabOwnerUserHandleForLaunch() {
- // If we are in work profile's process, return WorkProfile user as owner, otherwise we
- // always return PersonalProfile user as owner
- return UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())
- ? getWorkProfileUserHandle()
- : getPersonalProfileUserHandle();
+ // If we are in work or private profile's process, return WorkProfile/PrivateProfile user
+ // as owner, otherwise we always return PersonalProfile user as owner
+ if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
+ return getWorkProfileUserHandle();
+ } else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ return getPrivateProfileUserHandle();
+ }
+ return getPersonalProfileUserHandle();
}
private boolean hasWorkProfile() {
@@ -816,7 +842,15 @@
&& (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
}
+ protected final boolean isLaunchedAsPrivateProfile() {
+ return getPrivateProfileUserHandle() != null
+ && (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
+ }
+
protected boolean shouldShowTabs() {
+ if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ return false;
+ }
return hasWorkProfile() && ENABLE_TABBED_VIEW;
}
@@ -2619,6 +2653,11 @@
return resolveInfo.userHandle;
}
+ private boolean privateSpaceEnabled() {
+ return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+ }
+
/**
* An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
* invisible target in the list.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4..5705b7e 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
+import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -63,6 +64,8 @@
PackageMonitorCallback mPackageMonitorCallback;
+ private Executor mExecutor;
+
@UnsupportedAppUsage
public PackageMonitor() {
final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@
if (mPackageMonitorCallback == null) {
PackageManager pm = mRegisteredContext.getPackageManager();
if (pm != null) {
- mPackageMonitorCallback = new PackageMonitorCallback(this,
- new HandlerExecutor(mRegisteredHandler));
+ mExecutor = new HandlerExecutor(mRegisteredHandler);
+ mPackageMonitorCallback = new PackageMonitorCallback(this);
int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
}
@@ -131,6 +134,7 @@
}
mPackageMonitorCallback = null;
mRegisteredContext = null;
+ mExecutor = null;
}
public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@
doHandlePackageEvent(intent);
}
+
+ private void postHandlePackageEvent(Intent intent) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> doHandlePackageEvent(intent));
+ }
+ }
+
/**
* Handle the package related event
* @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@
}
private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+ private final WeakReference<PackageMonitor> mMonitorWeakReference;
- private final PackageMonitor mPackageMonitor;
- private final Executor mExecutor;
-
- PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
- mPackageMonitor = monitor;
- mExecutor = executor;
+ PackageMonitorCallback(PackageMonitor monitor) {
+ mMonitorWeakReference = new WeakReference<>(monitor);
}
@Override
@@ -537,7 +545,10 @@
Log.w(TAG, "No intent is set for PackageMonitorCallback");
return;
}
- mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+ PackageMonitor monitor = mMonitorWeakReference.get();
+ if (monitor != null) {
+ monitor.postHandlePackageEvent(intent);
+ }
}
}
}
diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
new file mode 100644
index 0000000..f05d9cb
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
@@ -0,0 +1,652 @@
+/*
+ * 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.internal.pm.parsing;
+
+import static com.android.internal.pm.pkg.SEInfoUtil.COMPLETE_STR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FallbackCategoryProvider;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.os.Debug;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.pkg.component.ComponentParseUtils;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import java.util.List;
+
+/**
+ * Method that use a {@link AndroidPackage} to generate a {@link PackageInfo} though
+ * the given {@link PackageManager.PackageInfoFlags}
+ * @hide
+ **/
+// TODO(b/317215254): refactor coped code from PackageInfoUtils
+public class PackageInfoCommonUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+ private static final boolean DEBUG = false;
+
+ /**
+ * Generates a {@link PackageInfo} from the given {@link AndroidPackage}
+ */
+ @Nullable
+ public static PackageInfo generate(@Nullable AndroidPackage pkg,
+ @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+ if (pkg == null) {
+ return null;
+ }
+ ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, userId);
+
+ PackageInfo info = new PackageInfo();
+ info.packageName = pkg.getPackageName();
+ info.splitNames = pkg.getSplitNames();
+ info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
+ info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
+ info.baseRevisionCode = pkg.getBaseRevisionCode();
+ info.splitRevisionCodes = pkg.getSplitRevisionCodes();
+ info.versionName = pkg.getVersionName();
+ info.sharedUserId = pkg.getSharedUserId();
+ info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+ info.applicationInfo = applicationInfo;
+ info.installLocation = pkg.getInstallLocation();
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ info.requiredForAllUsers = pkg.isRequiredForAllUsers();
+ }
+ info.restrictedAccountType = pkg.getRestrictedAccountType();
+ info.requiredAccountType = pkg.getRequiredAccountType();
+ info.overlayTarget = pkg.getOverlayTarget();
+ info.targetOverlayableName = pkg.getOverlayTargetOverlayableName();
+ info.overlayCategory = pkg.getOverlayCategory();
+ info.overlayPriority = pkg.getOverlayPriority();
+ info.mOverlayIsStatic = pkg.isOverlayIsStatic();
+ info.compileSdkVersion = pkg.getCompileSdkVersion();
+ info.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName();
+ info.isStub = pkg.isStub();
+ info.coreApp = pkg.isCoreApp();
+ info.isApex = pkg.isApex();
+
+ if ((flags & PackageManager.GET_CONFIGURATIONS) != 0) {
+ int size = pkg.getConfigPreferences().size();
+ if (size > 0) {
+ info.configPreferences = new ConfigurationInfo[size];
+ pkg.getConfigPreferences().toArray(info.configPreferences);
+ }
+ size = pkg.getRequestedFeatures().size();
+ if (size > 0) {
+ info.reqFeatures = new FeatureInfo[size];
+ pkg.getRequestedFeatures().toArray(info.reqFeatures);
+ }
+ size = pkg.getFeatureGroups().size();
+ if (size > 0) {
+ info.featureGroups = new FeatureGroupInfo[size];
+ pkg.getFeatureGroups().toArray(info.featureGroups);
+ }
+ }
+ if ((flags & PackageManager.GET_PERMISSIONS) != 0) {
+ int size = ArrayUtils.size(pkg.getPermissions());
+ if (size > 0) {
+ info.permissions = new PermissionInfo[size];
+ for (int i = 0; i < size; i++) {
+ final var permission = pkg.getPermissions().get(i);
+ final var permissionInfo = generatePermissionInfo(permission, flags);
+ info.permissions[i] = permissionInfo;
+ }
+ }
+ final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+ size = usesPermissions.size();
+ if (size > 0) {
+ info.requestedPermissions = new String[size];
+ info.requestedPermissionsFlags = new int[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ info.requestedPermissions[i] = usesPermission.getName();
+ // The notion of required permissions is deprecated but for compatibility.
+ info.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+ if ((usesPermission.getUsesPermissionFlags()
+ & ParsedUsesPermission.FLAG_NEVER_FOR_LOCATION) != 0) {
+ info.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+ }
+ if (pkg.getImplicitPermissions().contains(info.requestedPermissions[i])) {
+ info.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_IMPLICIT;
+ }
+ }
+ }
+ }
+ if ((flags & PackageManager.GET_ATTRIBUTIONS_LONG) != 0) {
+ int size = ArrayUtils.size(pkg.getAttributions());
+ if (size > 0) {
+ info.attributions = new Attribution[size];
+ for (int i = 0; i < size; i++) {
+ ParsedAttribution parsedAttribution = pkg.getAttributions().get(i);
+ if (parsedAttribution != null) {
+ info.attributions[i] = new Attribution(parsedAttribution.getTag(),
+ parsedAttribution.getLabel());
+ }
+ }
+ }
+ if (pkg.isAttributionsUserVisible()) {
+ info.applicationInfo.privateFlagsExt
+ |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ } else {
+ info.applicationInfo.privateFlagsExt
+ &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ }
+ } else {
+ info.applicationInfo.privateFlagsExt
+ &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ }
+
+ final SigningDetails signingDetails = pkg.getSigningDetails();
+ // deprecated method of getting signing certificates
+ if ((flags & PackageManager.GET_SIGNATURES) != 0) {
+ if (signingDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ info.signatures = new Signature[1];
+ info.signatures[0] = signingDetails.getPastSigningCertificates()[0];
+ } else if (signingDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = signingDetails.getSignatures().length;
+ info.signatures = new Signature[numberOfSigs];
+ System.arraycopy(signingDetails.getSignatures(), 0, info.signatures, 0,
+ numberOfSigs);
+ }
+ }
+
+ // replacement for GET_SIGNATURES
+ if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ if (signingDetails != SigningDetails.UNKNOWN) {
+ // only return a valid SigningInfo if there is signing information to report
+ info.signingInfo = new SigningInfo(signingDetails);
+ } else {
+ info.signingInfo = null;
+ }
+ }
+
+ if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+ final int size = pkg.getActivities().size();
+ if (size > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedActivity a = pkg.getActivities().get(i);
+ if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
+ a.getName())) {
+ continue;
+ }
+ res[num++] = generateActivityInfo(a, flags, applicationInfo);
+ }
+ }
+ info.activities = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+ final int size = pkg.getReceivers().size();
+ if (size > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedActivity a = pkg.getReceivers().get(i);
+ if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+ res[num++] = generateActivityInfo(a, flags, applicationInfo);
+ }
+ }
+ info.receivers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_SERVICES) != 0) {
+ final int size = pkg.getServices().size();
+ if (size > 0) {
+ int num = 0;
+ final ServiceInfo[] res = new ServiceInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedService s = pkg.getServices().get(i);
+ if (isMatch(pkg, s.isDirectBootAware(), flags)) {
+ res[num++] = generateServiceInfo(s, flags, applicationInfo);
+ }
+ }
+ info.services = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+ final int size = pkg.getProviders().size();
+ if (size > 0) {
+ int num = 0;
+ final ProviderInfo[] res = new ProviderInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedProvider pr = pkg.getProviders().get(i);
+ if (isMatch(pkg, pr.isDirectBootAware(), flags)) {
+ res[num++] = generateProviderInfo(pkg, pr, flags, applicationInfo, userId);
+ }
+ }
+ info.providers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_INSTRUMENTATION) != 0) {
+ final int size = pkg.getInstrumentations().size();
+ if (size > 0) {
+ info.instrumentation = new InstrumentationInfo[size];
+ for (int i = 0; i < size; i++) {
+ info.instrumentation[i] = generateInstrumentationInfo(
+ pkg.getInstrumentations().get(i), pkg, flags, userId);
+ }
+ }
+ }
+
+ return info;
+ }
+
+ private static void updateApplicationInfo(ApplicationInfo ai, long flags) {
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ ai.metaData = null;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) == 0) {
+ ai.sharedLibraryFiles = null;
+ ai.sharedLibraryInfos = null;
+ }
+
+ // CompatibilityMode is global state.
+ if (!ParsingPackageUtils.sCompatibilityModeEnabled) {
+ ai.disableCompatibilityMode();
+ }
+
+ if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
+ }
+ ai.seInfoUser = COMPLETE_STR;
+ }
+
+ @Nullable
+ private static ApplicationInfo generateApplicationInfo(@NonNull AndroidPackage pkg,
+ @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId) {
+
+ // Make shallow copy so we can store the metadata/libraries safely
+ ApplicationInfo info = ((AndroidPackageHidden) pkg).toAppInfoWithoutState();
+
+ updateApplicationInfo(info, flags);
+
+ initForUser(info, pkg, userId);
+
+ info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+ info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ info.metaData = pkg.getMetaData();
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+ List<String> usesLibraryFiles = pkg.getUsesLibraries();
+
+ info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+ ? null : usesLibraryFiles.toArray(new String[0]);
+ }
+
+ return info;
+ }
+
+ @Nullable
+ private static ActivityInfo generateActivityInfo(ParsedActivity a,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ if (a == null) return null;
+
+ // Make shallow copies so we can store the metadata safely
+ ActivityInfo ai = new ActivityInfo();
+ ai.targetActivity = a.getTargetActivity();
+ ai.processName = a.getProcessName();
+ ai.exported = a.isExported();
+ ai.theme = a.getTheme();
+ ai.uiOptions = a.getUiOptions();
+ ai.parentActivityName = a.getParentActivityName();
+ ai.permission = a.getPermission();
+ ai.taskAffinity = a.getTaskAffinity();
+ ai.flags = a.getFlags();
+ ai.privateFlags = a.getPrivateFlags();
+ ai.launchMode = a.getLaunchMode();
+ ai.documentLaunchMode = a.getDocumentLaunchMode();
+ ai.maxRecents = a.getMaxRecents();
+ ai.configChanges = a.getConfigChanges();
+ ai.softInputMode = a.getSoftInputMode();
+ ai.persistableMode = a.getPersistableMode();
+ ai.lockTaskLaunchMode = a.getLockTaskLaunchMode();
+ ai.screenOrientation = a.getScreenOrientation();
+ ai.resizeMode = a.getResizeMode();
+ ai.setMaxAspectRatio(a.getMaxAspectRatio());
+ ai.setMinAspectRatio(a.getMinAspectRatio());
+ ai.supportsSizeChanges = a.isSupportsSizeChanges();
+ ai.requestedVrComponent = a.getRequestedVrComponent();
+ ai.rotationAnimation = a.getRotationAnimation();
+ ai.colorMode = a.getColorMode();
+ ai.windowLayout = a.getWindowLayout();
+ ai.attributionTags = a.getAttributionTags();
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ var metaData = a.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ ai.metaData = metaData.isEmpty() ? null : metaData;
+ } else {
+ ai.metaData = null;
+ }
+ ai.applicationInfo = applicationInfo;
+ ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+ ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
+ assignFieldsComponentInfoParsedMainComponent(ai, a);
+ return ai;
+ }
+
+ @Nullable
+ private static ServiceInfo generateServiceInfo(ParsedService s,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ if (s == null) return null;
+
+ // Make shallow copies so we can store the metadata safely
+ ServiceInfo si = new ServiceInfo();
+ si.exported = s.isExported();
+ si.flags = s.getFlags();
+ si.permission = s.getPermission();
+ si.processName = s.getProcessName();
+ si.mForegroundServiceType = s.getForegroundServiceType();
+ si.applicationInfo = applicationInfo;
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ var metaData = s.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ si.metaData = metaData.isEmpty() ? null : metaData;
+ }
+ assignFieldsComponentInfoParsedMainComponent(si, s);
+ return si;
+ }
+
+ @Nullable
+ private static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull ApplicationInfo applicationInfo, int userId) {
+ if (p == null) return null;
+
+ if (!pkg.getPackageName().equals(applicationInfo.packageName)) {
+ Slog.wtf(TAG, "AppInfo's package name is different. Expected=" + pkg.getPackageName()
+ + " actual=" + applicationInfo.packageName);
+ applicationInfo = generateApplicationInfo(pkg, flags, userId);
+ }
+
+ // Make shallow copies so we can store the metadata safely
+ ProviderInfo pi = new ProviderInfo();
+ pi.exported = p.isExported();
+ pi.flags = p.getFlags();
+ pi.processName = p.getProcessName();
+ pi.authority = p.getAuthority();
+ pi.isSyncable = p.isSyncable();
+ pi.readPermission = p.getReadPermission();
+ pi.writePermission = p.getWritePermission();
+ pi.grantUriPermissions = p.isGrantUriPermissions();
+ pi.forceUriPermissions = p.isForceUriPermissions();
+ pi.multiprocess = p.isMultiProcess();
+ pi.initOrder = p.getInitOrder();
+ pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+ pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
+ if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+ pi.uriPermissionPatterns = null;
+ }
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ var metaData = p.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pi.metaData = metaData.isEmpty() ? null : metaData;
+ }
+ pi.applicationInfo = applicationInfo;
+ assignFieldsComponentInfoParsedMainComponent(pi, p);
+ return pi;
+ }
+
+ @Nullable
+ private static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
+ AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
+ if (i == null) return null;
+
+ InstrumentationInfo info = new InstrumentationInfo();
+ info.targetPackage = i.getTargetPackage();
+ info.targetProcesses = i.getTargetProcesses();
+ info.handleProfiling = i.isHandleProfiling();
+ info.functionalTest = i.isFunctionalTest();
+
+ info.sourceDir = pkg.getBaseApkPath();
+ info.publicSourceDir = pkg.getBaseApkPath();
+ info.splitNames = pkg.getSplitNames();
+ info.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+ info.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+ ? null : pkg.getSplitCodePaths();
+ info.splitDependencies = pkg.getSplitDependencies().size() == 0
+ ? null : pkg.getSplitDependencies();
+
+ initForUser(info, pkg, userId);
+
+ info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+ info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+ info.nativeLibraryDir = pkg.getNativeLibraryDir();
+ info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
+
+ assignFieldsPackageItemInfoParsedComponent(info, i);
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ info.metaData = null;
+ } else {
+ var metaData = i.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ info.metaData = metaData.isEmpty() ? null : metaData;
+ }
+
+ return info;
+ }
+
+ @Nullable
+ private static PermissionInfo generatePermissionInfo(ParsedPermission p,
+ @PackageManager.ComponentInfoFlagsBits long flags) {
+ // TODO(b/135203078): Remove null checks and make all usages @NonNull
+ if (p == null) return null;
+
+ PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission());
+
+ assignFieldsPackageItemInfoParsedComponent(pi, p);
+
+ pi.group = p.getGroup();
+ pi.requestRes = p.getRequestRes();
+ pi.protectionLevel = p.getProtectionLevel();
+ pi.descriptionRes = p.getDescriptionRes();
+ pi.flags = p.getFlags();
+ pi.knownCerts = p.getKnownCerts();
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ pi.metaData = null;
+ } else {
+ var metaData = p.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pi.metaData = metaData.isEmpty() ? null : metaData;
+ }
+ return pi;
+ }
+
+ private static void assignFieldsComponentInfoParsedMainComponent(
+ @NonNull ComponentInfo info, @NonNull ParsedMainComponent component) {
+ assignFieldsPackageItemInfoParsedComponent(info, component);
+ info.descriptionRes = component.getDescriptionRes();
+ info.directBootAware = component.isDirectBootAware();
+ info.enabled = component.isEnabled();
+ info.splitName = component.getSplitName();
+ info.attributionTags = component.getAttributionTags();
+ info.nonLocalizedLabel = component.getNonLocalizedLabel();
+ info.icon = component.getIcon();
+ }
+
+ private static void assignFieldsPackageItemInfoParsedComponent(
+ @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component) {
+ packageItemInfo.nonLocalizedLabel = ComponentParseUtils.getNonLocalizedLabel(component);
+ packageItemInfo.icon = ComponentParseUtils.getIcon(component);
+ packageItemInfo.banner = component.getBanner();
+ packageItemInfo.labelRes = component.getLabelRes();
+ packageItemInfo.logo = component.getLogo();
+ packageItemInfo.name = component.getName();
+ packageItemInfo.packageName = component.getPackageName();
+ }
+
+ private static void initForUser(ApplicationInfo output, AndroidPackage input,
+ @UserIdInt int userId) {
+ PackageImpl pkg = ((PackageImpl) input);
+ String packageName = input.getPackageName();
+ output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
+
+ // For performance reasons, all these paths are built as strings
+ final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+ final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+ if (credentialDir != null && deviceDir != null) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ output.credentialProtectedDataDir = credentialDir + packageName;
+ output.deviceProtectedDataDir = deviceDir + packageName;
+ } else {
+ // Convert /data/user/0/ -> /data/user/1/com.example.app
+ String userIdString = String.valueOf(userId);
+ int credentialLength = credentialDir.length();
+ output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+ .replace(credentialLength - 2, credentialLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ int deviceLength = deviceDir.length();
+ output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+ .replace(deviceLength - 2, deviceLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ }
+ }
+
+ if (input.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ output.dataDir = output.deviceProtectedDataDir;
+ } else {
+ output.dataDir = output.credentialProtectedDataDir;
+ }
+ }
+
+ // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
+ // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
+ private static void initForUser(InstrumentationInfo output, AndroidPackage input,
+ @UserIdInt int userId) {
+ PackageImpl pkg = ((PackageImpl) input);
+ String packageName = input.getPackageName();
+
+ // For performance reasons, all these paths are built as strings
+ final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+ final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+ if (credentialDir != null && deviceDir != null) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ output.credentialProtectedDataDir = credentialDir + packageName;
+ output.deviceProtectedDataDir = deviceDir + packageName;
+ } else {
+ // Convert /data/user/0/ -> /data/user/1/com.example.app
+ String userIdString = String.valueOf(userId);
+ int credentialLength = credentialDir.length();
+ output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+ .replace(credentialLength - 2, credentialLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ int deviceLength = deviceDir.length();
+ output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+ .replace(deviceLength - 2, deviceLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ }
+ }
+
+ if (input.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ output.dataDir = output.deviceProtectedDataDir;
+ } else {
+ output.dataDir = output.credentialProtectedDataDir;
+ }
+ }
+
+ /**
+ * Test if the given component is considered system, enabled and a match for the given
+ * flags.
+ *
+ * <p>
+ * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link
+ * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
+ * </p>
+ */
+ private static boolean isMatch(AndroidPackage pkg,
+ boolean isComponentDirectBootAware, long flags) {
+ final boolean isSystem = ((AndroidPackageHidden) pkg).isSystem();
+ if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+ if (!isSystem) {
+ return reportIfDebug(false, flags);
+ }
+ }
+
+ final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0)
+ && !isComponentDirectBootAware;
+ final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0)
+ && isComponentDirectBootAware;
+ return reportIfDebug(matchesUnaware || matchesAware, flags);
+ }
+
+ private static boolean reportIfDebug(boolean result, long flags) {
+ if (DEBUG && !result) {
+ Slog.i(TAG, "No match!; flags: "
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+ + Debug.getCaller());
+ }
+ return result;
+ }
+}
diff --git a/core/java/com/android/internal/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
index e413293..2c54672 100644
--- a/core/java/com/android/internal/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
@@ -40,6 +41,7 @@
import com.android.internal.util.ArrayUtils;
import java.io.File;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -78,10 +80,19 @@
displayMetrics.setToDefaults();
}
- PermissionManager permissionManager = ActivityThread.currentApplication()
- .getSystemService(PermissionManager.class);
- List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
- .getSplitPermissions();
+ List<PermissionManager.SplitPermissionInfo> splitPermissions = null;
+
+ final Application application = ActivityThread.currentApplication();
+ if (application != null) {
+ final PermissionManager permissionManager =
+ application.getSystemService(PermissionManager.class);
+ if (permissionManager != null) {
+ splitPermissions = permissionManager.getSplitPermissions();
+ }
+ }
+ if (splitPermissions == null) {
+ splitPermissions = new ArrayList<>();
+ }
mCacher = cacher;
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d1..56ea52d 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@
return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
}
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+ jobject sensor, jint index) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+ Vector<Sensor> sensorList;
+ ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+ if (ssize_t(index) >= count) {
+ return false;
+ }
+
+ return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
static void
nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
@@ -539,6 +552,9 @@
{"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
(void *)nativeGetSensorAtIndex},
+ {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+ (void *)nativeGetDefaultDeviceSensorAtIndex},
+
{"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
{"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 930b1a4..95d5049 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -65,7 +65,7 @@
mHandler = getInstrumentation().getContext().getMainThreadHandler();
mController = spy(ClientTransactionListenerController.createInstanceForTesting(
mDisplayManager));
- doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled();
+ doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
}
@Test
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 8308e7c..1617eda 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -20,12 +20,15 @@
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.SparseArray
import androidx.core.util.forEach
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
import org.junit.Rule
import kotlin.math.ceil
import kotlin.math.floor
@@ -45,6 +48,19 @@
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+
+ @Before
+ fun setup() {
+ defaultLookupTables = FontScaleConverterFactory.sLookupTables.clone()
+ }
+
+ @After
+ fun teardown() {
+ // Restore the default tables (since some tests will have added extras to the cache)
+ FontScaleConverterFactory.sLookupTables = defaultLookupTables
+ }
+
@Test
fun scale200IsTwiceAtSmallSizes() {
val table = FontScaleConverterFactory.forScale(2F)!!
@@ -245,7 +261,7 @@
}
companion object {
- private const val CONVERSION_TOLERANCE = 0.05f
+ private const val CONVERSION_TOLERANCE = 0.18f
}
}
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a5bbeb5..9292f66 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -16,7 +16,6 @@
package android.window.flags;
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
import android.platform.test.annotations.Presubmit;
@@ -39,12 +38,6 @@
public class WindowFlagsTest {
@Test
- public void testSyncWindowConfigUpdateFlag() {
- // No crash when accessing the flag.
- syncWindowConfigUpdateFlag();
- }
-
- @Test
public void testTaskFragmentSystemOrganizerFlag() {
// No crash when accessing the flag.
taskFragmentSystemOrganizerFlag();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b6813ff..b209c7c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,7 @@
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.allOf;
@@ -46,6 +47,7 @@
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.view.View;
import android.widget.RelativeLayout;
@@ -88,7 +90,8 @@
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
false);
-
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void cleanOverrideData() {
sOverrides.reset();
@@ -1156,6 +1159,97 @@
sOverrides.cloneProfileUserHandle)));
}
+ @Test
+ public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> privateResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+ setupResolverControllers(privateResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+ for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.privateProfileUserHandle);
+ }
+ }
+
+ @Test
+ public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> privateResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+ setupResolverControllers(privateResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+ for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.privateProfileUserHandle);
+ }
+ }
+
+ @Test
+ public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ activity.getPersonalProfileUserHandle());
+ }
+ }
+
+ @Test
+ public void testPrivateProfile_triggerFromWorkProfile(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertTrue(resolvedInfo.getResolveInfoAt(0).userHandle.equals(
+ activity.getPersonalProfileUserHandle()) || resolvedInfo.getResolveInfoAt(
+ 0).userHandle.equals(activity.getWorkProfileUserHandle()));
+ }
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -1237,6 +1331,10 @@
ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
}
+ private void markPrivateProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
@@ -1256,4 +1354,13 @@
eq(UserHandle.SYSTEM)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
}
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> resolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(resolvedComponentInfos));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index e193de0..862cbd5 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -88,6 +88,10 @@
return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
}
+ int getMultiProfilePagerAdapterCount(){
+ return mMultiProfilePagerAdapter.getCount();
+ }
+
@Override
public boolean isVoiceInteraction() {
if (sOverrides.isVoiceInteraction != null) {
@@ -144,6 +148,11 @@
}
@Override
+ protected UserHandle getPrivateProfileUserHandle() {
+ return sOverrides.privateProfileUserHandle;
+ }
+
+ @Override
protected UserHandle getTabOwnerUserHandleForLaunch() {
if (sOverrides.tabOwnerUserHandleForLaunch == null) {
return super.getTabOwnerUserHandleForLaunch();
@@ -176,6 +185,7 @@
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
public UserHandle cloneProfileUserHandle;
+ public UserHandle privateProfileUserHandle;
public UserHandle tabOwnerUserHandleForLaunch;
public Integer myUserId;
public boolean hasCrossProfileIntents;
@@ -191,6 +201,7 @@
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
cloneProfileUserHandle = null;
+ privateProfileUserHandle = null;
tabOwnerUserHandleForLaunch = null;
myUserId = null;
hasCrossProfileIntents = true;
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87..0000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 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.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d939..0000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187e..0000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2018 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.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 0000000..bb7d63e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_NonPlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ manifest: "static/AndroidManifest.xml",
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.signature_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde9..0000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 0000000..ee0c0e5
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_UpdateOverlay",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ static_libs: ["androidx.test.rules"],
+ aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["framework/v1/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["framework/v2/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["app/v1/res"],
+ manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["app/v2/res"],
+ manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887..0000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f6..13d38d2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
- <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
- otherwise, it may not be able to enforce provision for managed devices. -->
+ <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+ when app in background; otherwise, it may not be able to enforce provision for managed
+ devices. -->
<allow-in-power-save package="com.android.devicelockcontroller" />
+ <allow-in-data-usage-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f..b9efe65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@
<permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
<permission name="android.permission.BIND_WALLPAPER"/>
<permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
</privapp-permissions>
<privapp-permissions package="com.android.dynsystem">
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1..e4f793c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -30,8 +30,8 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="16dp">
-
+ android:paddingStart="6dp"
+ android:paddingEnd="8dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -43,7 +43,7 @@
android:id="@+id/application_name"
android:layout_width="0dp"
android:layout_height="20dp"
- android:minWidth="80dp"
+ android:maxWidth="86dp"
android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de61..0a40cea 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,25 @@
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+ <!-- Required empty space to be visible for partially offscreen tasks. -->
+ <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+ <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+ <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+ <!-- 32dp width back button + 10dp margin -->
+ <dimen name="caption_left_buttons_width">32dp</dimen>
+
+ <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+ <dimen name="caption_right_buttons_width">126dp</dimen>
+
+ <!-- 2 buttons * 48dp button size. -->
+ <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+
+ <!-- 22dp padding + 24dp app icon + 16dp expand button.
+ Text varies in size, we will calculate that width separately. -->
+ <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
<!-- The width of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba..af31f5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -35,7 +36,8 @@
/**
* The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null
+ || taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c6..72fba3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@
import android.window.TransitionFilter;
/**
- * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
*/
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
/**
- * Called when a transition changes the visibility of the home activity.
+ * Called when a transition changes the visibility of the home activity on the default display.
*/
void onHomeVisibilityChanged(in boolean isVisible);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b..6e7d11d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -34,6 +35,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -84,6 +86,69 @@
mDragPositioningCallback = dragPositioningCallback;
}
+ @Override
+ Rect calculateValidDragArea() {
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_left_buttons_width);
+
+ // On a smaller screen, don't require as much empty space on screen, as offscreen
+ // drags will be restricted too much.
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600
+ ? R.dimen.freeform_required_visible_empty_space_in_header :
+ R.dimen.small_screen_required_visible_empty_space_in_header;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ requiredEmptySpaceId);
+
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_right_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+ displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6ec91e0..3b6be8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -443,6 +443,66 @@
}
/**
+ * Determine valid drag area for this task based on elements in the app chip.
+ */
+ @Override
+ Rect calculateValidDragArea() {
+ final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+ mWindowDecorViewHolder).getAppNameTextWidth();
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.freeform_required_visible_empty_space_in_header);
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_right_edge_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth, displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
+ /**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c7..677c7f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -162,18 +162,29 @@
/**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
- * stable bounds.
+ * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+ * valid drag area.
*/
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
- PointF repositionStartPoint, float x, float y) {
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ PointF repositionStartPoint, float x, float y, Rect validDragArea) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
x, y);
+ snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
+ }
- // If task is outside of stable bounds (in the status bar area), shift the task down.
- if (stableBounds.top > repositionTaskBounds.top) {
- final int yShift = stableBounds.top - repositionTaskBounds.top;
- repositionTaskBounds.offset(0, yShift);
+ private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ // If we were never supplied a valid drag area, do not restrict movement.
+ // Otherwise, we restrict deltas to keep task position inside the Rect.
+ if (validDragArea.width() == 0) return;
+ if (repositionTaskBounds.left < validDragArea.left) {
+ repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ } else if (repositionTaskBounds.left > validDragArea.right) {
+ repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ }
+ if (repositionTaskBounds.top < validDragArea.top) {
+ repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ } else if (repositionTaskBounds.top > validDragArea.bottom) {
+ repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e..5d006fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -136,7 +136,8 @@
y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0c..4363558 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -152,7 +152,8 @@
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mDesktopWindowDecoration.calculateValidDragArea());
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b755..ee0e31e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@
import static android.view.WindowInsets.Type.statusBars;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -178,6 +179,13 @@
*/
abstract void relayout(RunningTaskInfo taskInfo);
+ /**
+ * Used by the {@link DragPositioningCallback} associated with the implementing class to
+ * enforce drags ending in a valid position. A null result means no restriction.
+ */
+ @Nullable
+ abstract Rect calculateValidDragArea();
+
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a813..144373f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -42,6 +42,8 @@
private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+ val appNameTextWidth: Int
+ get() = appNameTextView.width
init {
captionView.setOnTouchListener(onCaptionTouchListener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04a..e60be71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@
}
@Test
+ fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100)
+
+ DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ validDragArea)
+ assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+ assertThat(repositionTaskBounds.right)
+ .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+ assertThat(repositionTaskBounds.bottom)
+ .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+ }
+
+ @Test
fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
var hasMoved = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2..2ce49cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -103,6 +103,7 @@
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -660,6 +661,38 @@
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -761,5 +794,11 @@
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf1..a759b53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -118,6 +118,7 @@
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -379,6 +380,38 @@
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -470,5 +503,11 @@
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e42f74..fe508e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -702,6 +702,11 @@
relayout(taskInfo, false /* applyStartTransactionOnDraw */);
}
+ @Override
+ Rect calculateValidDragArea() {
+ return null;
+ }
+
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw) {
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000..67f4775 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -37,6 +40,23 @@
* @hide
*/
public final class LocationResult implements Parcelable {
+ private static final String TAG = "LocationResult";
+
+ // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+ // could likely be lower, but we only want to throw out really absurd values.
+ private static final float MAX_ACCURACY_M = 1000000;
+
+ // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+ // current fastest private jet). Higher speed than the value is considered as a malfunction
+ // than a correct reading.
+ private static final float MAX_SPEED_MPS = 343;
+
+ /** Exception representing an invalid location within a {@link LocationResult}. */
+ public static class BadLocationException extends Exception {
+ public BadLocationException(String message) {
+ super(message);
+ }
+ }
/**
* Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@
*
* @hide
*/
- public @NonNull LocationResult validate() {
+ public @NonNull LocationResult validate() throws BadLocationException {
long prevElapsedRealtimeNs = 0;
final int size = mLocations.size();
for (int i = 0; i < size; ++i) {
Location location = mLocations.get(i);
- if (!location.isComplete()) {
- throw new IllegalArgumentException(
- "incomplete location at index " + i + ": " + mLocations);
- }
- if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
- throw new IllegalArgumentException(
- "incorrectly ordered location at index " + i + ": " + mLocations);
+ if (Flags.locationValidation()) {
+ if (location.getLatitude() < -90.0
+ || location.getLatitude() > 90.0
+ || location.getLongitude() < -180.0
+ || location.getLongitude() > 180.0
+ || Double.isNaN(location.getLatitude())
+ || Double.isNaN(location.getLongitude())) {
+ throw new BadLocationException("location must have valid lat/lng");
+ }
+ if (!location.hasAccuracy()) {
+ throw new BadLocationException("location must have accuracy");
+ }
+ if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+ throw new BadLocationException("location must have reasonable accuracy");
+ }
+ if (location.getTime() < 0) {
+ throw new BadLocationException("location must have valid time");
+ }
+ if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+ throw new BadLocationException(
+ "location must have valid monotonically increasing realtime");
+ }
+ if (location.getElapsedRealtimeNanos()
+ > SystemClock.elapsedRealtimeNanos()) {
+ throw new BadLocationException("location must not have realtime in the future");
+ }
+ if (!location.isMock()) {
+ if (location.getProvider() == null) {
+ throw new BadLocationException("location must have valid provider");
+ }
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ throw new BadLocationException("location must not be at 0,0");
+ }
+ }
+
+ if (location.hasSpeed() && (location.getSpeed() < 0
+ || location.getSpeed() > MAX_SPEED_MPS)) {
+ Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+ location.removeSpeed();
+ }
+ } else {
+ if (!location.isComplete()) {
+ throw new IllegalArgumentException(
+ "incomplete location at index " + i + ": " + mLocations);
+ }
+ if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+ throw new IllegalArgumentException(
+ "incorrectly ordered location at index " + i + ": " + mLocations);
+ }
}
prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index f4b1056..a8464d3 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -27,3 +27,10 @@
description: "Flag for releasing SUPL connection on timeout"
bug: "315024652"
}
+
+flag {
+ name: "location_validation"
+ namespace: "location"
+ description: "Flag for location validation"
+ bug: "314328533"
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3da52cc..7f95886 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -77,3 +77,9 @@
bug: "279555229"
}
+flag {
+ name: "enable_notifying_activity_manager_with_media_session_status_change"
+ namespace: "media_solutions"
+ description: "Notify ActivityManager with the changes in playback state of the media session."
+ bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6..60497fe 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@
return false;
}
+ /**
+ * Returns whether the service holding the media session should run in the foreground when the
+ * media session has this playback state or not.
+ *
+ * @hide
+ */
+ public boolean shouldAllowServiceToRunInForeground() {
+ switch (mState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 96e95fd..3fcb871 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@
mpuSequenceNumber, isPesPrivateData, sc,
audioDescriptor.get(), presentationsJObj.get()));
+ // Protect mFilterClient from being set to null.
+ android::Mutex::Autolock autoLock(mLock);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@
}
}
}
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterEventID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterEventID;
+
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(mFilterObj, nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, array.get());
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterEvent:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterEventID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterEventID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, array.get());
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ALOGV("FilterClientCallbackImpl::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterStatusID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterStatusID;
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(filter.get(), nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterStatus:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterStatusID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterStatusID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
}
void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = false;
@@ -979,6 +995,7 @@
void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = true;
@@ -1047,11 +1064,14 @@
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
+ }
+ mFilterClient = nullptr;
}
- mFilterClient = nullptr;
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998d..3de3ab9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ android::Mutex mLock;
jclass mEventClass;
jclass mSectionEventClass;
jclass mMediaEventClass;
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb4..1ac5db6 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- style="@style/ScrollViewStyle">
+ style="@style/ScrollViewStyle"
+ android:importantForAccessibility="no">
<LinearLayout
- android:id="@+id/data_transfer_confirmation"
- style="@style/ContainerLayout">
-
- <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
-
- <ImageView
- android:id="@+id/header_icon"
- android:layout_width="match_parent"
- android:layout_height="32dp"
- android:gravity="center"
- android:layout_marginTop="18dp"
- android:src="@drawable/ic_warning"
- android:contentDescription="@null" />
-
- <LinearLayout style="@style/Description">
-
- <TextView
- android:id="@+id/title"
- style="@style/DescriptionTitle" />
-
- <TextView
- android:id="@+id/summary"
- style="@style/DescriptionSummary" />
-
- </LinearLayout>
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:importantForAccessibility="no">
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="18dp">
+ android:id="@+id/data_transfer_confirmation"
+ style="@style/ContainerLayout">
- <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+ <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
- <Button
- android:id="@+id/btn_positive"
- style="@style/PositiveButton"
- android:text="@string/consent_yes" />
+ <ImageView
+ android:id="@+id/header_icon"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:gravity="center"
+ android:layout_marginTop="18dp"
+ android:src="@drawable/ic_warning"
+ android:contentDescription="@null" />
- <Button
- android:id="@+id/btn_negative"
- style="@style/NegativeButton"
- android:text="@string/consent_no" />
+ <LinearLayout style="@style/Description">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/DescriptionTitle" />
+
+ <TextView
+ android:id="@+id/summary"
+ style="@style/DescriptionSummary" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="18dp">
+
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+ <Button
+ android:id="@+id/btn_positive"
+ style="@style/PositiveButton"
+ android:text="@string/consent_yes" />
+
+ <Button
+ android:id="@+id/btn_negative"
+ style="@style/NegativeButton"
+ android:text="@string/consent_no" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11..6a4bb21 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@
@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)
+ @KeepTarget(instanceOfClassConstant = LocalTransport.class)
})
@Override
public String name() {
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 25ad9b8..98a5a67 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -35,7 +35,10 @@
name: "PackageInstaller",
defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
certificate: "platform",
privileged: true,
@@ -62,7 +65,10 @@
name: "PackageInstaller_tablet",
defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
certificate: "platform",
privileged: true,
@@ -91,7 +97,10 @@
name: "PackageInstaller_tv",
defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
certificate: "platform",
privileged: true,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
deleted file mode 100644
index c8175ad..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
+++ /dev/null
@@ -1,912 +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.packageinstaller.v2.model;
-
-import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
-import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
-import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_DONE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.InstallSourceInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.ApplicationInfoFlags;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.InstallEventReceiver;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallReady;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import java.io.File;
-import java.io.IOException;
-
-public class InstallRepository {
-
- public static final String EXTRA_STAGED_SESSION_ID =
- "com.android.packageinstaller.extra.STAGED_SESSION_ID";
- private static final String SCHEME_PACKAGE = "package";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
- private static final String TAG = InstallRepository.class.getSimpleName();
- private final Context mContext;
- private final PackageManager mPackageManager;
- private final PackageInstaller mPackageInstaller;
- private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
- private final AppOpsManager mAppOpsManager;
- private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
- private final MutableLiveData<InstallStage> mInstallResult = new MutableLiveData<>();
- private final boolean mLocalLOGV = false;
- private Intent mIntent;
- private boolean mIsSessionInstall;
- private boolean mIsTrustedSource;
- /**
- * Session ID for a session created when caller uses PackageInstaller APIs
- */
- private int mSessionId;
- /**
- * Session ID for a session created by this app
- */
- private int mStagedSessionId = SessionInfo.INVALID_ID;
- private int mCallingUid;
- private String mCallingPackage;
- private SessionStager mSessionStager;
- private AppOpRequestInfo mAppOpRequestInfo;
- private AppSnippet mAppSnippet;
- /**
- * PackageInfo of the app being installed on device.
- */
- private PackageInfo mNewPackageInfo;
-
- public InstallRepository(Context context) {
- mContext = context;
- mPackageManager = context.getPackageManager();
- mPackageInstaller = mPackageManager.getPackageInstaller();
- mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
- mUserManager = context.getSystemService(UserManager.class);
- mAppOpsManager = context.getSystemService(AppOpsManager.class);
- }
-
- /**
- * Extracts information from the incoming install intent, checks caller's permission to install
- * packages, verifies that the caller is the install session owner (in case of a session based
- * install) and checks if the current user has restrictions set that prevent app installation,
- *
- * @param intent the incoming {@link Intent} object for installing a package
- * @param callerInfo {@link CallerInfo} that holds the callingUid and callingPackageName
- * @return <p>{@link InstallAborted} if there are errors while performing the checks</p>
- * <p>{@link InstallStaging} after successfully performing the checks</p>
- */
- public InstallStage performPreInstallChecks(Intent intent, CallerInfo callerInfo) {
- mIntent = intent;
-
- String callingAttributionTag = null;
-
- mIsSessionInstall =
- PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
- || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
-
- mSessionId = mIsSessionInstall
- ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
- : SessionInfo.INVALID_ID;
-
- mStagedSessionId = mIntent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID);
-
- mCallingPackage = callerInfo.getPackageName();
-
- if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) {
- PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(mSessionId);
- mCallingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
- callingAttributionTag =
- (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
- }
-
- // Uid of the source package, coming from ActivityManager
- mCallingUid = callerInfo.getUid();
- if (mCallingUid == Process.INVALID_UID) {
- Log.e(TAG, "Could not determine the launching uid.");
- }
- final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
- // Uid of the source package, with a preference to uid from ApplicationInfo
- final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
- mAppOpRequestInfo = new AppOpRequestInfo(
- getPackageNameForUid(mContext, originatingUid, mCallingPackage),
- originatingUid, callingAttributionTag);
-
- if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
- // Caller's identity could not be determined. Abort the install
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- if ((mSessionId != SessionInfo.INVALID_ID
- && !isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId))
- || (mStagedSessionId != SessionInfo.INVALID_ID
- && !isCallerSessionOwner(mPackageInstaller, Process.myUid(), mStagedSessionId))) {
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- mIsTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, mIntent, originatingUid);
-
- if (!isInstallPermissionGrantedOrRequested(mContext, mCallingUid, originatingUid,
- mIsTrustedSource)) {
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- String restriction = getDevicePolicyRestrictions();
- if (restriction != null) {
- InstallAborted.Builder abortedBuilder =
- new InstallAborted.Builder(ABORT_REASON_POLICY).setMessage(restriction);
- final Intent adminSupportDetailsIntent =
- mDevicePolicyManager.createAdminSupportIntent(restriction);
- if (adminSupportDetailsIntent != null) {
- abortedBuilder.setResultIntent(adminSupportDetailsIntent);
- }
- return abortedBuilder.build();
- }
-
- maybeRemoveInvalidInstallerPackageName(callerInfo);
-
- return new InstallStaging();
- }
-
- /**
- * @return the ApplicationInfo for the installation source (the calling package), if available
- */
- @Nullable
- private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
- if (callingPackage == null) {
- return null;
- }
- try {
- return mPackageManager.getApplicationInfo(callingPackage, 0);
- } catch (PackageManager.NameNotFoundException ignored) {
- return null;
- }
- }
-
- private boolean isInstallRequestFromTrustedSource(ApplicationInfo sourceInfo, Intent intent,
- int originatingUid) {
- boolean isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
- return sourceInfo != null && sourceInfo.isPrivilegedApp()
- && (isNotUnknownSource
- || isPermissionGranted(mContext, Manifest.permission.INSTALL_PACKAGES, originatingUid));
- }
-
- private String getDevicePolicyRestrictions() {
- final String[] restrictions = new String[]{
- UserManager.DISALLOW_INSTALL_APPS,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
- };
-
- for (String restriction : restrictions) {
- if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
- continue;
- }
- return restriction;
- }
- return null;
- }
-
- private void maybeRemoveInvalidInstallerPackageName(CallerInfo callerInfo) {
- final String installerPackageNameFromIntent =
- mIntent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
- if (installerPackageNameFromIntent == null) {
- return;
- }
- if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.getPackageName())
- && !isPermissionGranted(mPackageManager, Manifest.permission.INSTALL_PACKAGES,
- callerInfo.getPackageName())) {
- Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
- + " is invalid. Remove it.");
- EventLog.writeEvent(0x534e4554, "236687884", callerInfo.getUid(),
- "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
- mIntent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
- }
- }
-
- public void stageForInstall() {
- Uri uri = mIntent.getData();
- if (mStagedSessionId != SessionInfo.INVALID_ID
- || mIsSessionInstall
- || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) {
- // For a session based install or installing with a package:// URI, there is no file
- // for us to stage.
- mStagingResult.setValue(new InstallReady());
- return;
- }
- if (uri != null
- && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
- && canPackageQuery(mContext, mCallingUid, uri)) {
-
- if (mStagedSessionId > 0) {
- final PackageInstaller.SessionInfo info =
- mPackageInstaller.getSessionInfo(mStagedSessionId);
- if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
- Log.w(TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
- if (info != null) {
- cleanupStagingSession();
- }
- mStagedSessionId = 0;
- }
- }
-
- // Session does not exist, or became invalid.
- if (mStagedSessionId <= 0) {
- // Create session here to be able to show error.
- try (final AssetFileDescriptor afd =
- mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) {
- ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
- PackageInstaller.SessionParams params =
- createSessionParams(mIntent, pfd, uri.toString());
- mStagedSessionId = mPackageInstaller.createSession(params);
- } catch (IOException e) {
- Log.w(TAG, "Failed to create a staging session", e);
- mStagingResult.setValue(
- new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build());
- return;
- }
- }
-
- SessionStageListener listener = new SessionStageListener() {
- @Override
- public void onStagingSuccess(SessionInfo info) {
- //TODO: Verify if the returned sessionInfo should be used anywhere
- mStagingResult.setValue(new InstallReady());
- }
-
- @Override
- public void onStagingFailure() {
- cleanupStagingSession();
- mStagingResult.setValue(
- new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build());
- }
- };
- if (mSessionStager != null) {
- mSessionStager.cancel(true);
- }
- mSessionStager = new SessionStager(mContext, uri, mStagedSessionId, listener);
- mSessionStager.execute();
- }
- }
-
- public int getStagedSessionId() {
- return mStagedSessionId;
- }
-
- private void cleanupStagingSession() {
- if (mStagedSessionId > 0) {
- try {
- mPackageInstaller.abandonSession(mStagedSessionId);
- } catch (SecurityException ignored) {
- }
- mStagedSessionId = 0;
- }
- }
-
- private PackageInstaller.SessionParams createSessionParams(@NonNull Intent intent,
- @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri.class);
- params.setPackageSource(
- referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
- : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
- params.setInstallAsInstantApp(false);
- params.setReferrerUri(referrerUri);
- params.setOriginatingUri(
- intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri.class));
- params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- Process.INVALID_UID));
- params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME));
- params.setInstallReason(PackageManager.INSTALL_REASON_USER);
- // Disable full screen intent usage by for sideloads.
- params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
- PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
-
- if (pfd != null) {
- try {
- final PackageInstaller.InstallInfo result = mPackageInstaller.readInstallInfo(pfd,
- debugPathName, 0);
- params.setAppPackageName(result.getPackageName());
- params.setInstallLocation(result.getInstallLocation());
- params.setSize(result.calculateInstalledSize(params, pfd));
- } catch (PackageInstaller.PackageParsingException e) {
- Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
- params.setSize(pfd.getStatSize());
- } catch (IOException e) {
- Log.e(TAG,
- "Cannot calculate installed size " + debugPathName
- + ". Try only apk size.", e);
- }
- } else {
- Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
- }
- return params;
- }
-
- /**
- * Processes Install session, file:// or package:// URI to generate data pertaining to user
- * confirmation for an install. This method also checks if the source app has the AppOp granted
- * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
- * be reused once appOp has been granted
- *
- * @return <ul>
- * <li>InstallAborted </li>
- * <ul>
- * <li> If install session is invalid (not sealed or resolvedBaseApk path
- * is invalid) </li>
- * <li> Source app doesn't have visibility to target app </li>
- * <li> The APK is invalid </li>
- * <li> URI is invalid </li>
- * <li> Can't get ApplicationInfo for source app, to request AppOp </li>
- * </ul>
- * <li> InstallUserActionRequired</li>
- * <ul>
- * <li> If AppOP is granted and user action is required to proceed
- * with install </li>
- * <li> If AppOp grant is to be requested from the user</li>
- * </ul>
- * </ul>
- */
- public InstallStage requestUserConfirmation() {
- if (mIsTrustedSource) {
- if (mLocalLOGV) {
- Log.i(TAG, "install allowed");
- }
- // Returns InstallUserActionRequired stage if install details could be successfully
- // computed, else it returns InstallAborted.
- return generateConfirmationSnippet();
- } else {
- InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
- if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
- // Source app already has appOp granted.
- return generateConfirmationSnippet();
- } else {
- return unknownSourceStage;
- }
- }
- }
-
-
- private InstallStage generateConfirmationSnippet() {
- final Object packageSource;
- int pendingUserActionReason = -1;
- if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(mIntent.getAction())) {
- final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
- String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
-
- if (info == null || !info.isSealed() || resolvedPath == null) {
- Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- packageSource = Uri.fromFile(new File(resolvedPath));
- // TODO: Not sure where is this used yet. PIA.java passes it to
- // InstallInstalling if not null
- // mOriginatingURI = null;
- // mReferrerURI = null;
- pendingUserActionReason = info.getPendingUserActionReason();
- } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(mIntent.getAction())) {
- final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
-
- if (info == null || !info.isPreApprovalRequested()) {
- Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- packageSource = info;
- // mOriginatingURI = null;
- // mReferrerURI = null;
- pendingUserActionReason = info.getPendingUserActionReason();
- } else {
- // Two possible origins:
- // 1. Installation with SCHEME_PACKAGE.
- // 2. Installation with "file://" for session created by this app
- if (mIntent.getData() != null && mIntent.getData().getScheme().equals(SCHEME_PACKAGE)) {
- packageSource = mIntent.getData();
- } else {
- SessionInfo stagedSessionInfo = mPackageInstaller.getSessionInfo(mStagedSessionId);
- packageSource = Uri.fromFile(new File(stagedSessionInfo.getResolvedBaseApkPath()));
- }
- // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
- // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
- pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;
- }
-
- // if there's nothing to do, quietly slip into the ether
- if (packageSource == null) {
- Log.w(TAG, "Unspecified source");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_URI))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build();
- }
-
- return processAppSnippet(packageSource, pendingUserActionReason);
- }
-
- /**
- * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
- * session) to set up the installer for this install.
- *
- * @param source The source of package URI or SessionInfo
- * @return {@code true} iff the installer could be set up
- */
- private InstallStage processAppSnippet(Object source, int userActionReason) {
- if (source instanceof Uri) {
- return processPackageUri((Uri) source, userActionReason);
- } else if (source instanceof SessionInfo) {
- return processSessionInfo((SessionInfo) source, userActionReason);
- }
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- /**
- * Parse the Uri and set up the installer for this package.
- *
- * @param packageUri The URI to parse
- * @return {@code true} iff the installer could be set up
- */
- private InstallStage processPackageUri(final Uri packageUri, int userActionReason) {
- final String scheme = packageUri.getScheme();
- final String packageName = packageUri.getSchemeSpecificPart();
-
- if (scheme == null) {
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- if (mLocalLOGV) {
- Log.i(TAG, "processPackageUri(): uri = " + packageUri + ", scheme = " + scheme);
- }
-
- switch (scheme) {
- case SCHEME_PACKAGE -> {
- for (UserHandle handle : mUserManager.getUserHandles(true)) {
- PackageManager pmForUser = mContext.createContextAsUser(handle, 0)
- .getPackageManager();
- try {
- if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
- mNewPackageInfo = pmForUser.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS
- | PackageManager.MATCH_UNINSTALLED_PACKAGES);
- }
- } catch (NameNotFoundException ignored) {
- }
- }
- if (mNewPackageInfo == null) {
- Log.w(TAG, "Requested package " + packageUri.getSchemeSpecificPart()
- + " not available. Discontinuing installation");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setErrorDialogType(DLG_PACKAGE_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build();
- }
- mAppSnippet = getAppSnippet(mContext, mNewPackageInfo);
- if (mLocalLOGV) {
- Log.i(TAG, "Created snippet for " + mAppSnippet.getLabel());
- }
- }
- case ContentResolver.SCHEME_FILE -> {
- File sourceFile = new File(packageUri.getPath());
- mNewPackageInfo = getPackageInfo(mContext, sourceFile,
- PackageManager.GET_PERMISSIONS);
-
- // Check for parse errors
- if (mNewPackageInfo == null) {
- Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setErrorDialogType(DLG_PACKAGE_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build();
- }
- if (mLocalLOGV) {
- Log.i(TAG, "Creating snippet for local file " + sourceFile);
- }
- mAppSnippet = getAppSnippet(mContext, mNewPackageInfo.applicationInfo, sourceFile);
- }
- default -> {
- Log.e(TAG, "Unexpected URI scheme " + packageUri);
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- }
-
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
- .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
- .setAppUpdating(isAppUpdating(mNewPackageInfo))
- .build();
- }
-
- /**
- * Use the SessionInfo and set up the installer for pre-commit install session.
- *
- * @param sessionInfo The SessionInfo to compose
- * @return {@code true} iff the installer could be set up
- */
- private InstallStage processSessionInfo(@NonNull SessionInfo sessionInfo,
- int userActionReason) {
- mNewPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName());
-
- mAppSnippet = getAppSnippet(mContext, sessionInfo);
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
- .setAppUpdating(isAppUpdating(mNewPackageInfo))
- .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
- .build();
- }
-
- private String getUpdateMessage(PackageInfo pkgInfo, int userActionReason) {
- if (isAppUpdating(pkgInfo)) {
- final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo);
- final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
-
- if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
- && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
- return mContext.getString(R.string.install_confirm_question_update_owner_reminder,
- requestedUpdateOwnerLabel, existingUpdateOwnerLabel);
- }
- }
- return null;
- }
-
- private CharSequence getExistingUpdateOwnerLabel(PackageInfo pkgInfo) {
- try {
- final String packageName = pkgInfo.packageName;
- final InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName);
- final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
- return getApplicationLabel(existingUpdateOwner);
- } catch (NameNotFoundException e) {
- return null;
- }
- }
-
- private CharSequence getApplicationLabel(String packageName) {
- try {
- final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
- ApplicationInfoFlags.of(0));
- return mPackageManager.getApplicationLabel(appInfo);
- } catch (NameNotFoundException e) {
- return null;
- }
- }
-
- private boolean isAppUpdating(PackageInfo newPkgInfo) {
- String pkgName = newPkgInfo.packageName;
- // Check if there is already a package on the device with this name
- // but it has been renamed to something else.
- String[] oldName = mPackageManager.canonicalToCurrentPackageNames(new String[]{pkgName});
- if (oldName != null && oldName.length > 0 && oldName[0] != null) {
- pkgName = oldName[0];
- newPkgInfo.packageName = pkgName;
- newPkgInfo.applicationInfo.packageName = pkgName;
- }
- // Check if package is already installed. display confirmation dialog if replacing pkg
- try {
- // This is a little convoluted because we want to get all uninstalled
- // apps, but this may include apps with just data, and if it is just
- // data we still want to count it as "installed".
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkgName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES);
- if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
- return false;
- }
- } catch (NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
- /**
- * Once the user returns from Settings related to installing from unknown sources, reattempt
- * the installation if the source app is granted permission to install other apps. Abort the
- * installation if the source app is still not granted installing permission.
- * @return {@link InstallUserActionRequired} containing data required to ask user confirmation
- * to proceed with the install.
- * {@link InstallAborted} if there was an error while recomputing, or the source still
- * doesn't have install permission.
- */
- public InstallStage reattemptInstall() {
- InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
- if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
- // Source app now has appOp granted.
- return generateConfirmationSnippet();
- } else if (unknownSourceStage.getStageCode() == InstallStage.STAGE_ABORTED) {
- // There was some error in determining the AppOp code for the source app.
- // Abort installation
- return unknownSourceStage;
- } else {
- // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
- // unexpected while reattempting the install. Let's abort it.
- Log.e(TAG, "AppOp still not granted.");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- }
-
- private InstallStage handleUnknownSources(AppOpRequestInfo requestInfo) {
- if (requestInfo.getCallingPackage() == null) {
- Log.i(TAG, "No source found for package " + mNewPackageInfo.packageName);
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_ANONYMOUS_SOURCE, null)
- .build();
- }
- // Shouldn't use static constant directly, see b/65534401.
- final String appOpStr =
- AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
- final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr,
- requestInfo.getOriginatingUid(),
- requestInfo.getCallingPackage(), requestInfo.getAttributionTag(),
- "Started package installation activity");
-
- if (mLocalLOGV) {
- Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
- }
- switch (appOpMode) {
- case AppOpsManager.MODE_DEFAULT:
- mAppOpsManager.setMode(appOpStr, requestInfo.getOriginatingUid(),
- requestInfo.getCallingPackage(), AppOpsManager.MODE_ERRORED);
- // fall through
- case AppOpsManager.MODE_ERRORED:
- try {
- ApplicationInfo sourceInfo =
- mPackageManager.getApplicationInfo(requestInfo.getCallingPackage(), 0);
- AppSnippet sourceAppSnippet = getAppSnippet(mContext, sourceInfo);
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_UNKNOWN_SOURCE, sourceAppSnippet)
- .setDialogMessage(requestInfo.getCallingPackage())
- .build();
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Did not find appInfo for " + requestInfo.getCallingPackage());
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- case AppOpsManager.MODE_ALLOWED:
- return new InstallReady();
- default:
- Log.e(TAG, "Invalid app op mode " + appOpMode
- + " for OP_REQUEST_INSTALL_PACKAGES found for uid "
- + requestInfo.getOriginatingUid());
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- }
-
-
- /**
- * Kick off the installation. Register a broadcast listener to get the result of the
- * installation and commit the staged session here. If the installation was session based,
- * signal the PackageInstaller that the user has granted permission to proceed with the install
- */
- public void initiateInstall() {
- if (mSessionId > 0) {
- mPackageInstaller.setPermissionsResult(mSessionId, true);
- mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_DONE)
- .setActivityResultCode(Activity.RESULT_OK).build());
- return;
- }
-
- Uri uri = mIntent.getData();
- if (uri != null && SCHEME_PACKAGE.equals(uri.getScheme())) {
- try {
- mPackageManager.installExistingPackage(mNewPackageInfo.packageName);
- setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null, -1);
- } catch (PackageManager.NameNotFoundException e) {
- setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
- }
- return;
- }
-
- if (mStagedSessionId <= 0) {
- // How did we even land here?
- Log.e(TAG, "Invalid local session and caller initiated session");
- mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .build());
- return;
- }
-
- int installId;
- try {
- mInstallResult.setValue(new InstallInstalling(mAppSnippet));
- installId = InstallEventReceiver.addObserver(mContext,
- EventResultPersister.GENERATE_NEW_ID, this::setStageBasedOnResult);
- } catch (EventResultPersister.OutOfIdsException e) {
- setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
- return;
- }
-
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.setPackage(mContext.getPackageName());
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId);
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext, installId, broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
- try {
- PackageInstaller.Session session = mPackageInstaller.openSession(mStagedSessionId);
- session.commit(pendingIntent.getIntentSender());
- } catch (Exception e) {
- Log.e(TAG, "Session " + mStagedSessionId + " could not be opened.", e);
- mPackageInstaller.abandonSession(mStagedSessionId);
- setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
- }
- }
-
- private void setStageBasedOnResult(int statusCode, int legacyStatus, String message,
- int serviceId) {
- if (statusCode == PackageInstaller.STATUS_SUCCESS) {
- boolean shouldReturnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-
- InstallSuccess.Builder successBuilder = new InstallSuccess.Builder(mAppSnippet)
- .setShouldReturnResult(shouldReturnResult);
- Intent resultIntent;
- if (shouldReturnResult) {
- resultIntent = new Intent()
- .putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
- } else {
- resultIntent = mPackageManager
- .getLaunchIntentForPackage(mNewPackageInfo.packageName);
- }
- successBuilder.setResultIntent(resultIntent);
-
- mInstallResult.setValue(successBuilder.build());
- } else {
- mInstallResult.setValue(
- new InstallFailed(mAppSnippet, statusCode, legacyStatus, message));
- }
- }
-
- public MutableLiveData<InstallStage> getInstallResult() {
- return mInstallResult;
- }
-
- /**
- * Cleanup the staged session. Also signal the packageinstaller that an install session is to
- * be aborted
- */
- public void cleanupInstall() {
- if (mSessionId > 0) {
- mPackageInstaller.setPermissionsResult(mSessionId, false);
- } else if (mStagedSessionId > 0) {
- cleanupStagingSession();
- }
- }
-
- /**
- * When the identity of the install source could not be determined, user can skip checking the
- * source and directly proceed with the install.
- */
- public InstallStage forcedSkipSourceCheck() {
- return generateConfirmationSnippet();
- }
-
- public MutableLiveData<Integer> getStagingProgress() {
- if (mSessionStager != null) {
- return mSessionStager.getProgress();
- }
- return new MutableLiveData<>(0);
- }
-
- public MutableLiveData<InstallStage> getStagingResult() {
- return mStagingResult;
- }
-
- public interface SessionStageListener {
-
- void onStagingSuccess(SessionInfo info);
-
- void onStagingFailure();
- }
-
- public static class CallerInfo {
-
- private final String mPackageName;
- private final int mUid;
-
- public CallerInfo(String packageName, int uid) {
- mPackageName = packageName;
- mUid = uid;
- }
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public int getUid() {
- return mUid;
- }
- }
-
- public static class AppOpRequestInfo {
-
- private String mCallingPackage;
- private String mAttributionTag;
- private int mOrginatingUid;
-
- public AppOpRequestInfo(String callingPackage, int orginatingUid, String attributionTag) {
- mCallingPackage = callingPackage;
- mOrginatingUid = orginatingUid;
- mAttributionTag = attributionTag;
- }
-
- public String getCallingPackage() {
- return mCallingPackage;
- }
-
- public String getAttributionTag() {
- return mAttributionTag;
- }
-
- public int getOriginatingUid() {
- return mOrginatingUid;
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
new file mode 100644
index 0000000..326e533
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -0,0 +1,867 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionInfo
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import android.os.Process
+import android.os.UserManager
+import android.text.TextUtils
+import android.util.EventLog
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.InstallEventReceiver
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_UNKNOWN_SOURCE
+import com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery
+import com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner
+import com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import java.io.File
+import java.io.IOException
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+class InstallRepository(private val context: Context) {
+
+ private val packageManager: PackageManager = context.packageManager
+ private val packageInstaller: PackageInstaller = packageManager.packageInstaller
+ private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+ private val devicePolicyManager: DevicePolicyManager? =
+ context.getSystemService(DevicePolicyManager::class.java)
+ private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+ private val localLOGV = false
+ private var isSessionInstall = false
+ private var isTrustedSource = false
+ private val _stagingResult = MutableLiveData<InstallStage>()
+ val stagingResult: LiveData<InstallStage>
+ get() = _stagingResult
+ private val _installResult = MutableLiveData<InstallStage>()
+ val installResult: LiveData<InstallStage>
+ get() = _installResult
+
+ /**
+ * Session ID for a session created when caller uses PackageInstaller APIs
+ */
+ private var sessionId = SessionInfo.INVALID_ID
+
+ /**
+ * Session ID for a session created by this app
+ */
+ var stagedSessionId = SessionInfo.INVALID_ID
+ private set
+ private var callingUid = Process.INVALID_UID
+ private var callingPackage: String? = null
+ private var sessionStager: SessionStager? = null
+ private lateinit var intent: Intent
+ private lateinit var appOpRequestInfo: AppOpRequestInfo
+ private lateinit var appSnippet: PackageUtil.AppSnippet
+
+ /**
+ * PackageInfo of the app being installed on device.
+ */
+ private var newPackageInfo: PackageInfo? = null
+
+ /**
+ * Extracts information from the incoming install intent, checks caller's permission to install
+ * packages, verifies that the caller is the install session owner (in case of a session based
+ * install) and checks if the current user has restrictions set that prevent app installation,
+ *
+ * @param intent the incoming [Intent] object for installing a package
+ * @param callerInfo [CallerInfo] that holds the callingUid and callingPackageName
+ * @return
+ * * [InstallAborted] if there are errors while performing the checks
+ * * [InstallStaging] after successfully performing the checks
+ */
+ fun performPreInstallChecks(intent: Intent, callerInfo: CallerInfo): InstallStage {
+ this.intent = intent
+
+ var callingAttributionTag: String? = null
+
+ isSessionInstall =
+ PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action
+ || PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action
+
+ sessionId = if (isSessionInstall)
+ intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
+ else SessionInfo.INVALID_ID
+
+ stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
+
+ callingPackage = callerInfo.packageName
+
+ if (callingPackage == null && sessionId != SessionInfo.INVALID_ID) {
+ val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId)
+ callingPackage = sessionInfo?.getInstallerPackageName()
+ callingAttributionTag = sessionInfo?.getInstallerAttributionTag()
+ }
+
+ // Uid of the source package, coming from ActivityManager
+ callingUid = callerInfo.uid
+ if (callingUid == Process.INVALID_UID) {
+ Log.e(LOG_TAG, "Could not determine the launching uid.")
+ }
+ val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
+ // Uid of the source package, with a preference to uid from ApplicationInfo
+ val originatingUid = sourceInfo?.uid ?: callingUid
+ appOpRequestInfo = AppOpRequestInfo(
+ getPackageNameForUid(context, originatingUid, callingPackage),
+ originatingUid, callingAttributionTag
+ )
+
+ if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+ // Caller's identity could not be determined. Abort the install
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+
+ if ((sessionId != SessionInfo.INVALID_ID
+ && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+ || (stagedSessionId != SessionInfo.INVALID_ID
+ && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
+ ) {
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+
+ isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
+ if (!isInstallPermissionGrantedOrRequested(
+ context, callingUid, originatingUid, isTrustedSource
+ )
+ ) {
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+
+ val restriction = getDevicePolicyRestrictions()
+ if (restriction != null) {
+ val adminSupportDetailsIntent =
+ devicePolicyManager!!.createAdminSupportIntent(restriction)
+ return InstallAborted(
+ ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
+ )
+ }
+
+ maybeRemoveInvalidInstallerPackageName(callerInfo)
+
+ return InstallStaging()
+ }
+
+ /**
+ * @return the ApplicationInfo for the installation source (the calling package), if available
+ */
+ private fun getSourceInfo(callingPackage: String?): ApplicationInfo? {
+ return try {
+ callingPackage?.let { packageManager.getApplicationInfo(it, 0) }
+ } catch (ignored: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ private fun isInstallRequestFromTrustedSource(
+ sourceInfo: ApplicationInfo?,
+ intent: Intent,
+ originatingUid: Int,
+ ): Boolean {
+ val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
+ return (sourceInfo != null && sourceInfo.isPrivilegedApp
+ && (isNotUnknownSource
+ || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+ }
+
+ private fun getDevicePolicyRestrictions(): String? {
+ val restrictions = arrayOf(
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ )
+ for (restriction in restrictions) {
+ if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+ continue
+ }
+ return restriction
+ }
+ return null
+ }
+
+ private fun maybeRemoveInvalidInstallerPackageName(callerInfo: CallerInfo) {
+ val installerPackageNameFromIntent =
+ intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME) ?: return
+
+ if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.packageName)
+ && callerInfo.packageName != null
+ && isPermissionGranted(
+ packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName
+ )
+ ) {
+ Log.e(
+ LOG_TAG, "The given installer package name $installerPackageNameFromIntent"
+ + " is invalid. Remove it."
+ )
+ EventLog.writeEvent(
+ 0x534e4554, "236687884", callerInfo.uid,
+ "Invalid EXTRA_INSTALLER_PACKAGE_NAME"
+ )
+ intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME)
+ }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ fun stageForInstall() {
+ val uri = intent.data
+ if (stagedSessionId != SessionInfo.INVALID_ID
+ || isSessionInstall
+ || (uri != null && SCHEME_PACKAGE == uri.scheme)
+ ) {
+ // For a session based install or installing with a package:// URI, there is no file
+ // for us to stage.
+ _stagingResult.value = InstallReady()
+ return
+ }
+ if (uri != null
+ && ContentResolver.SCHEME_CONTENT == uri.scheme
+ && canPackageQuery(context, callingUid, uri)
+ ) {
+ if (stagedSessionId > 0) {
+ val info: SessionInfo? = packageInstaller.getSessionInfo(stagedSessionId)
+ if (info == null || !info.isActive || info.resolvedBaseApkPath == null) {
+ Log.w(LOG_TAG, "Session $stagedSessionId in funky state; ignoring")
+ if (info != null) {
+ cleanupStagingSession()
+ }
+ stagedSessionId = 0
+ }
+ }
+
+ // Session does not exist, or became invalid.
+ if (stagedSessionId <= 0) {
+ // Create session here to be able to show error.
+ try {
+ context.contentResolver.openAssetFileDescriptor(uri, "r").use { afd ->
+ val pfd: ParcelFileDescriptor? = afd?.parcelFileDescriptor
+ val params: SessionParams =
+ createSessionParams(intent, pfd, uri.toString())
+ stagedSessionId = packageInstaller.createSession(params)
+ }
+ } catch (e: IOException) {
+ Log.w(LOG_TAG, "Failed to create a staging session", e)
+ _stagingResult.value = InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ return
+ }
+ }
+
+ sessionStager = SessionStager(context, uri, stagedSessionId)
+ GlobalScope.launch(Dispatchers.Main) {
+ val wasFileStaged = sessionStager!!.execute()
+
+ if (wasFileStaged) {
+ _stagingResult.value = InstallReady()
+ } else {
+ cleanupStagingSession()
+ _stagingResult.value = InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ }
+ }
+ }
+
+ private fun cleanupStagingSession() {
+ if (stagedSessionId > 0) {
+ try {
+ packageInstaller.abandonSession(stagedSessionId)
+ } catch (ignored: SecurityException) {
+ }
+ stagedSessionId = 0
+ }
+ }
+
+ private fun createSessionParams(
+ intent: Intent,
+ pfd: ParcelFileDescriptor?,
+ debugPathName: String,
+ ): SessionParams {
+ val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
+ val referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
+ params.setPackageSource(
+ if (referrerUri != null)
+ PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+ else PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ )
+ params.setInstallAsInstantApp(false)
+ params.setReferrerUri(referrerUri)
+ params.setOriginatingUri(
+ intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri::class.java)
+ )
+ params.setOriginatingUid(
+ intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, Process.INVALID_UID)
+ )
+ params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME))
+ params.setInstallReason(PackageManager.INSTALL_REASON_USER)
+ // Disable full screen intent usage by for sideloads.
+ params.setPermissionState(
+ Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
+ )
+ if (pfd != null) {
+ try {
+ val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
+ params.setAppPackageName(installInfo.packageName)
+ params.setInstallLocation(installInfo.installLocation)
+ params.setSize(installInfo.calculateInstalledSize(params, pfd))
+ } catch (e: PackageInstaller.PackageParsingException) {
+ Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
+ params.setSize(pfd.statSize)
+ } catch (e: IOException) {
+ Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+ "Try only apk size.", e
+ )
+ }
+ } else {
+ Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.")
+ }
+ return params
+ }
+
+ /**
+ * Processes Install session, file:// or package:// URI to generate data pertaining to user
+ * confirmation for an install. This method also checks if the source app has the AppOp granted
+ * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
+ * be reused once appOp has been granted
+ *
+ * @return
+ * * [InstallAborted]
+ * * If install session is invalid (not sealed or resolvedBaseApk path is invalid)
+ * * Source app doesn't have visibility to target app
+ * * The APK is invalid
+ * * URI is invalid
+ * * Can't get ApplicationInfo for source app, to request AppOp
+ *
+ * * [InstallUserActionRequired]
+ * * If AppOP is granted and user action is required to proceed with install
+ * * If AppOp grant is to be requested from the user
+ */
+ fun requestUserConfirmation(): InstallStage {
+ return if (isTrustedSource) {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "install allowed")
+ }
+ // Returns InstallUserActionRequired stage if install details could be successfully
+ // computed, else it returns InstallAborted.
+ generateConfirmationSnippet()
+ } else {
+ val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+ if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) {
+ // Source app already has appOp granted.
+ generateConfirmationSnippet()
+ } else {
+ unknownSourceStage
+ }
+ }
+ }
+
+ private fun generateConfirmationSnippet(): InstallStage {
+ val packageSource: Any?
+ val pendingUserActionReason: Int
+
+ if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
+ val info = packageInstaller.getSessionInfo(sessionId)
+ val resolvedPath = info?.resolvedBaseApkPath
+ if (info == null || !info.isSealed || resolvedPath == null) {
+ Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ packageSource = Uri.fromFile(File(resolvedPath))
+ // TODO: Not sure where is this used yet. PIA.java passes it to
+ // InstallInstalling if not null
+ // mOriginatingURI = null;
+ // mReferrerURI = null;
+ pendingUserActionReason = info.getPendingUserActionReason()
+ } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action) {
+ val info = packageInstaller.getSessionInfo(sessionId)
+ if (info == null || !info.isPreApprovalRequested) {
+ Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ packageSource = info
+ // mOriginatingURI = null;
+ // mReferrerURI = null;
+ pendingUserActionReason = info.getPendingUserActionReason()
+ } else {
+ // Two possible origins:
+ // 1. Installation with SCHEME_PACKAGE.
+ // 2. Installation with "file://" for session created by this app
+ packageSource =
+ if (intent.data?.scheme == SCHEME_PACKAGE) {
+ intent.data
+ } else {
+ val stagedSessionInfo = packageInstaller.getSessionInfo(stagedSessionId)
+ Uri.fromFile(File(stagedSessionInfo?.resolvedBaseApkPath!!))
+ }
+ // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
+ // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
+ pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE
+ }
+
+ // if there's nothing to do, quietly slip into the ether
+ if (packageSource == null) {
+ Log.w(LOG_TAG, "Unspecified source")
+ return InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT,
+ PackageManager.INSTALL_FAILED_INVALID_URI
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ return processAppSnippet(packageSource, pendingUserActionReason)
+ }
+
+ /**
+ * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
+ * session) to set up the installer for this install.
+ *
+ * @param source The source of package URI or SessionInfo
+ * @return
+ * * [InstallUserActionRequired] if source could be processed
+ * * [InstallAborted] if source is invalid or there was an error is processing a source
+ */
+ private fun processAppSnippet(source: Any, userActionReason: Int): InstallStage {
+ return when (source) {
+ is Uri -> processPackageUri(source, userActionReason)
+ is SessionInfo -> processSessionInfo(source, userActionReason)
+ else -> InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+
+ /**
+ * Parse the Uri and set up the installer for this package.
+ *
+ * @param packageUri The URI to parse
+ * @return
+ * * [InstallUserActionRequired] if source could be processed
+ * * [InstallAborted] if source is invalid or there was an error is processing a source
+ */
+ private fun processPackageUri(packageUri: Uri, userActionReason: Int): InstallStage {
+ val scheme = packageUri.scheme
+ val packageName = packageUri.schemeSpecificPart
+ if (scheme == null) {
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ if (localLOGV) {
+ Log.i(LOG_TAG, "processPackageUri(): uri = $packageUri, scheme = $scheme")
+ }
+ when (scheme) {
+ SCHEME_PACKAGE -> {
+ for (handle in userManager!!.getUserHandles(true)) {
+ val pmForUser = context.createContextAsUser(handle, 0).packageManager
+ try {
+ if (pmForUser.canPackageQuery(callingPackage!!, packageName)) {
+ newPackageInfo = pmForUser.getPackageInfo(
+ packageName,
+ PackageManager.GET_PERMISSIONS
+ or PackageManager.MATCH_UNINSTALLED_PACKAGES
+ )
+ }
+ } catch (ignored: PackageManager.NameNotFoundException) {
+ }
+ }
+ if (newPackageInfo == null) {
+ Log.w(
+ LOG_TAG, "Requested package " + packageUri.schemeSpecificPart
+ + " not available. Discontinuing installation"
+ )
+ return InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ errorDialogType = DLG_PACKAGE_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ appSnippet = getAppSnippet(context, newPackageInfo!!)
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Created snippet for " + appSnippet.label)
+ }
+ }
+
+ ContentResolver.SCHEME_FILE -> {
+ val sourceFile = packageUri.path?.let { File(it) }
+ newPackageInfo = sourceFile?.let {
+ getPackageInfo(context, it, PackageManager.GET_PERMISSIONS)
+ }
+
+ // Check for parse errors
+ if (newPackageInfo == null) {
+ Log.w(
+ LOG_TAG, "Parse error when parsing manifest. " +
+ "Discontinuing installation"
+ )
+ return InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ errorDialogType = DLG_PACKAGE_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT,
+ PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Creating snippet for local file $sourceFile")
+ }
+ appSnippet = getAppSnippet(context, newPackageInfo!!, sourceFile!!)
+ }
+
+ else -> {
+ Log.e(LOG_TAG, "Unexpected URI scheme $packageUri")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+ return InstallUserActionRequired(
+ USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+ getUpdateMessage(newPackageInfo!!, userActionReason)
+ )
+ }
+
+ /**
+ * Use the SessionInfo and set up the installer for pre-commit install session.
+ *
+ * @param sessionInfo The SessionInfo to compose
+ * @return
+ * * [InstallUserActionRequired] if source could be processed
+ * * [InstallAborted] if source is invalid or there was an error is processing a source
+ */
+ private fun processSessionInfo(sessionInfo: SessionInfo, userActionReason: Int): InstallStage {
+ newPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName())
+ appSnippet = getAppSnippet(context, sessionInfo)
+
+ return InstallUserActionRequired(
+ USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+ getUpdateMessage(newPackageInfo!!, userActionReason)
+
+ )
+ }
+
+ private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? {
+ if (isAppUpdating(pkgInfo)) {
+ val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
+ val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage)
+ if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
+ && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
+ ) {
+ return context.getString(
+ R.string.install_confirm_question_update_owner_reminder,
+ requestedUpdateOwnerLabel, existingUpdateOwnerLabel
+ )
+ }
+ }
+ return null
+ }
+
+ private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? {
+ return try {
+ val packageName = pkgInfo.packageName
+ val sourceInfo = packageManager.getInstallSourceInfo(packageName)
+ val existingUpdateOwner = sourceInfo.updateOwnerPackageName
+ getApplicationLabel(existingUpdateOwner)
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ private fun getApplicationLabel(packageName: String?): CharSequence? {
+ return try {
+ val appInfo = packageName?.let {
+ packageManager.getApplicationInfo(
+ it, PackageManager.ApplicationInfoFlags.of(0)
+ )
+ }
+ appInfo?.let { packageManager.getApplicationLabel(it) }
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ private fun isAppUpdating(newPkgInfo: PackageInfo): Boolean {
+ var pkgName = newPkgInfo.packageName
+ // Check if there is already a package on the device with this name
+ // but it has been renamed to something else.
+ val oldName = packageManager.canonicalToCurrentPackageNames(arrayOf(pkgName))
+ if (oldName != null && oldName.isNotEmpty() && oldName[0] != null) {
+ pkgName = oldName[0]
+ newPkgInfo.packageName = pkgName
+ newPkgInfo.applicationInfo?.packageName = pkgName
+ }
+
+ // Check if package is already installed. display confirmation dialog if replacing pkg
+ try {
+ // This is a little convoluted because we want to get all uninstalled
+ // apps, but this may include apps with just data, and if it is just
+ // data we still want to count it as "installed".
+ val appInfo = packageManager.getApplicationInfo(
+ pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES
+ )
+ if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
+ return false
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ return false
+ }
+ return true
+ }
+
+ /**
+ * Once the user returns from Settings related to installing from unknown sources, reattempt
+ * the installation if the source app is granted permission to install other apps. Abort the
+ * installation if the source app is still not granted installing permission.
+ *
+ * @return
+ * * [InstallUserActionRequired] containing data required to ask user confirmation
+ * to proceed with the install.
+ * * [InstallAborted] if there was an error while recomputing, or the source still
+ * doesn't have install permission.
+ */
+ fun reattemptInstall(): InstallStage {
+ val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+ return when (unknownSourceStage.stageCode) {
+ InstallStage.STAGE_READY -> {
+ // Source app now has appOp granted.
+ generateConfirmationSnippet()
+ }
+
+ InstallStage.STAGE_ABORTED -> {
+ // There was some error in determining the AppOp code for the source app.
+ // Abort installation
+ unknownSourceStage
+ }
+
+ else -> {
+ // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
+ // unexpected while reattempting the install. Let's abort it.
+ Log.e(LOG_TAG, "AppOp still not granted.")
+ InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+ }
+
+ private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
+ if (requestInfo.callingPackage == null) {
+ Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
+ return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
+ }
+ // Shouldn't use static constant directly, see b/65534401.
+ val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
+ val appOpMode = appOpsManager!!.noteOpNoThrow(
+ appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+ requestInfo.attributionTag, "Started package installation activity"
+ )
+ if (localLOGV) {
+ Log.i(LOG_TAG, "handleUnknownSources(): appMode=$appOpMode")
+ }
+
+ return when (appOpMode) {
+ AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
+ if (appOpMode == AppOpsManager.MODE_DEFAULT) {
+ appOpsManager.setMode(
+ appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+ AppOpsManager.MODE_ERRORED
+ )
+ }
+ try {
+ val sourceInfo =
+ packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+ val sourceAppSnippet = getAppSnippet(context, sourceInfo)
+ InstallUserActionRequired(
+ USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
+ dialogMessage = requestInfo.callingPackage
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+ InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+
+ AppOpsManager.MODE_ALLOWED -> InstallReady()
+
+ else -> {
+ Log.e(
+ LOG_TAG, "Invalid app op mode $appOpMode for " +
+ "OP_REQUEST_INSTALL_PACKAGES found for uid $requestInfo.originatingUid"
+ )
+ InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+ }
+
+ /**
+ * Kick off the installation. Register a broadcast listener to get the result of the
+ * installation and commit the staged session here. If the installation was session based,
+ * signal the PackageInstaller that the user has granted permission to proceed with the install
+ */
+ fun initiateInstall() {
+ if (sessionId > 0) {
+ packageInstaller.setPermissionsResult(sessionId, true)
+ _installResult.value = InstallAborted(
+ ABORT_REASON_DONE, activityResultCode = Activity.RESULT_OK
+ )
+ return
+ }
+ val uri = intent.data
+ if (SCHEME_PACKAGE == uri?.scheme) {
+ try {
+ packageManager.installExistingPackage(
+ newPackageInfo!!.packageName, PackageManager.INSTALL_REASON_USER
+ )
+ setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
+ } catch (e: PackageManager.NameNotFoundException) {
+ setStageBasedOnResult(
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+ null)
+ }
+ return
+ }
+ if (stagedSessionId <= 0) {
+ // How did we even land here?
+ Log.e(LOG_TAG, "Invalid local session and caller initiated session")
+ _installResult.value = InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ return
+ }
+ val installId: Int
+ try {
+ _installResult.value = InstallInstalling(appSnippet)
+ installId = InstallEventReceiver.addObserver(
+ context, EventResultPersister.GENERATE_NEW_ID
+ ) { statusCode: Int, legacyStatus: Int, message: String?, serviceId: Int ->
+ setStageBasedOnResult(statusCode, legacyStatus, message)
+ }
+ } catch (e: OutOfIdsException) {
+ setStageBasedOnResult(
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ return
+ }
+ val broadcastIntent = Intent(BROADCAST_ACTION)
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ broadcastIntent.setPackage(context.packageName)
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, installId, broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ try {
+ val session = packageInstaller.openSession(stagedSessionId)
+ session.commit(pendingIntent.intentSender)
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
+ packageInstaller.abandonSession(stagedSessionId)
+ setStageBasedOnResult(
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ }
+ }
+
+ private fun setStageBasedOnResult(
+ statusCode: Int,
+ legacyStatus: Int,
+ message: String?
+ ) {
+ if (statusCode == PackageInstaller.STATUS_SUCCESS) {
+ val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+ val resultIntent = if (shouldReturnResult) {
+ Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
+ } else {
+ packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName)
+ }
+ _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
+ } else {
+ _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+ }
+ }
+
+ /**
+ * Cleanup the staged session. Also signal the packageinstaller that an install session is to
+ * be aborted
+ */
+ fun cleanupInstall() {
+ if (sessionId > 0) {
+ packageInstaller.setPermissionsResult(sessionId, false)
+ } else if (stagedSessionId > 0) {
+ cleanupStagingSession()
+ }
+ }
+
+ /**
+ * When the identity of the install source could not be determined, user can skip checking the
+ * source and directly proceed with the install.
+ */
+ fun forcedSkipSourceCheck(): InstallStage {
+ return generateConfirmationSnippet()
+ }
+
+ val stagingProgress: LiveData<Int>
+ get() = sessionStager?.progress ?: MutableLiveData(0)
+
+ companion object {
+ const val EXTRA_STAGED_SESSION_ID = "com.android.packageinstaller.extra.STAGED_SESSION_ID"
+ const val SCHEME_PACKAGE = "package"
+ const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT"
+ private val LOG_TAG = InstallRepository::class.java.simpleName
+ }
+
+ data class CallerInfo(val packageName: String?, val uid: Int)
+ data class AppOpRequestInfo(
+ val callingPackage: String?,
+ val originatingUid: Int,
+ val attributionTag: String?,
+ )
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
new file mode 100644
index 0000000..be49b39
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -0,0 +1,134 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+
+sealed class InstallStage(val stageCode: Int) {
+
+ companion object {
+ const val STAGE_DEFAULT = -1
+ const val STAGE_ABORTED = 0
+ const val STAGE_STAGING = 1
+ const val STAGE_READY = 2
+ const val STAGE_USER_ACTION_REQUIRED = 3
+ const val STAGE_INSTALLING = 4
+ const val STAGE_SUCCESS = 5
+ const val STAGE_FAILED = 6
+ }
+}
+
+class InstallStaging : InstallStage(STAGE_STAGING)
+
+class InstallReady : InstallStage(STAGE_READY)
+
+data class InstallUserActionRequired(
+ val actionReason: Int,
+ private val appSnippet: PackageUtil.AppSnippet? = null,
+ val isAppUpdating: Boolean = false,
+ val dialogMessage: String? = null,
+) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
+
+ val appIcon: Drawable?
+ get() = appSnippet?.icon
+
+ val appLabel: String?
+ get() = appSnippet?.let { appSnippet.label as String? }
+
+ companion object {
+ const val USER_ACTION_REASON_UNKNOWN_SOURCE = 0
+ const val USER_ACTION_REASON_ANONYMOUS_SOURCE = 1
+ const val USER_ACTION_REASON_INSTALL_CONFIRMATION = 2
+ }
+}
+
+data class InstallInstalling(private val appSnippet: PackageUtil.AppSnippet) :
+ InstallStage(STAGE_INSTALLING) {
+
+ val appIcon: Drawable?
+ get() = appSnippet.icon
+
+ val appLabel: String?
+ get() = appSnippet.label as String?
+}
+
+data class InstallSuccess(
+ private val appSnippet: PackageUtil.AppSnippet,
+ val shouldReturnResult: Boolean = false,
+ /**
+ *
+ * * If the caller is requesting a result back, this will hold the Intent with
+ * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED] which is sent
+ * back to the caller.
+ *
+ * * If the caller doesn't want the result back, this will hold the Intent that launches
+ * the newly installed / updated app if a launchable activity exists.
+ */
+ val resultIntent: Intent? = null,
+) : InstallStage(STAGE_SUCCESS) {
+
+ val appIcon: Drawable?
+ get() = appSnippet.icon
+
+ val appLabel: String?
+ get() = appSnippet.label as String?
+}
+
+data class InstallFailed(
+ private val appSnippet: PackageUtil.AppSnippet,
+ val legacyCode: Int,
+ val statusCode: Int,
+ val message: String?,
+) : InstallStage(STAGE_FAILED) {
+
+ val appIcon: Drawable?
+ get() = appSnippet.icon
+
+ val appLabel: String?
+ get() = appSnippet.label as String?
+}
+
+data class InstallAborted(
+ val abortReason: Int,
+ /**
+ * It will hold the restriction name, when the restriction was enforced by the system, and not
+ * a device admin.
+ */
+ val message: String? = null,
+ /**
+ * * If abort reason is [ABORT_REASON_POLICY], then this will hold the Intent
+ * to display a support dialog when a feature was disabled by an admin. It will be
+ * `null` if the feature is disabled by the system. In this case, the restriction name
+ * will be set in [message]
+ * * If the abort reason is [ABORT_REASON_INTERNAL_ERROR], it **may** hold an
+ * intent to be sent as a result to the calling activity.
+ */
+ val resultIntent: Intent? = null,
+ val activityResultCode: Int = Activity.RESULT_CANCELED,
+ val errorDialogType: Int? = 0,
+) : InstallStage(STAGE_ABORTED) {
+
+ companion object {
+ const val ABORT_REASON_INTERNAL_ERROR = 0
+ const val ABORT_REASON_POLICY = 1
+ const val ABORT_REASON_DONE = 2
+ const val DLG_PACKAGE_ERROR = 1
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
deleted file mode 100644
index fe05237..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
+++ /dev/null
@@ -1,462 +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.packageinstaller.v2.model;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import java.io.File;
-import java.util.Arrays;
-import java.util.Objects;
-
-public class PackageUtil {
-
- private static final String TAG = InstallRepository.class.getSimpleName();
- private static final String DOWNLOADS_AUTHORITY = "downloads";
- private static final String SPLIT_BASE_APK_END_WITH = "base.apk";
-
- /**
- * Determines if the UID belongs to the system downloads provider and returns the
- * {@link ApplicationInfo} of the provider
- *
- * @param uid UID of the caller
- * @return {@link ApplicationInfo} of the provider if a downloads provider exists, it is a
- * system app, and its UID matches with the passed UID, null otherwise.
- */
- public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
- final ProviderInfo providerInfo = pm.resolveContentProvider(
- DOWNLOADS_AUTHORITY, 0);
- if (providerInfo == null) {
- // There seems to be no currently enabled downloads provider on the system.
- return null;
- }
- ApplicationInfo appInfo = providerInfo.applicationInfo;
- if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
- return appInfo;
- }
- return null;
- }
-
- /**
- * Get the maximum target sdk for a UID.
- *
- * @param context The context to use
- * @param uid The UID requesting the install/uninstall
- * @return The maximum target SDK or -1 if the uid does not match any packages.
- */
- public static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
- PackageManager pm = context.getPackageManager();
- final String[] packages = pm.getPackagesForUid(uid);
- int targetSdkVersion = -1;
- if (packages != null) {
- for (String packageName : packages) {
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore and try the next package
- }
- }
- }
- return targetSdkVersion;
- }
-
- public static boolean canPackageQuery(Context context, int callingUid, Uri packageUri) {
- PackageManager pm = context.getPackageManager();
- ProviderInfo info = pm.resolveContentProvider(packageUri.getAuthority(),
- PackageManager.ComponentInfoFlags.of(0));
- if (info == null) {
- return false;
- }
- String targetPackage = info.packageName;
-
- String[] callingPackages = pm.getPackagesForUid(callingUid);
- if (callingPackages == null) {
- return false;
- }
- for (String callingPackage : callingPackages) {
- try {
- if (pm.canPackageQuery(callingPackage, targetPackage)) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- // no-op
- }
- }
- return false;
- }
-
- /**
- * @param context the {@link Context} object
- * @param permission the permission name to check
- * @param callingUid the UID of the caller who's permission is being checked
- * @return {@code true} if the callingUid is granted the said permission
- */
- public static boolean isPermissionGranted(Context context, String permission, int callingUid) {
- return context.checkPermission(permission, -1, callingUid)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * @param pm the {@link PackageManager} object
- * @param permission the permission name to check
- * @param packageName the name of the package who's permission is being checked
- * @return {@code true} if the package is granted the said permission
- */
- public static boolean isPermissionGranted(PackageManager pm, String permission,
- String packageName) {
- return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * @param context the {@link Context} object
- * @param callingUid the UID of the caller who's permission is being checked
- * @param originatingUid the UID from where install is being originated. This could be same as
- * callingUid or it will be the UID of the package performing a session based install
- * @param isTrustedSource whether install request is coming from a privileged app or an app that
- * has {@link Manifest.permission.INSTALL_PACKAGES} permission granted
- * @return {@code true} if the package is granted the said permission
- */
- public static boolean isInstallPermissionGrantedOrRequested(Context context, int callingUid,
- int originatingUid, boolean isTrustedSource) {
- boolean isDocumentsManager =
- isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid);
- boolean isSystemDownloadsProvider =
- getSystemDownloadsProviderInfo(context.getPackageManager(), callingUid) != null;
-
- if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
-
- final int targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid);
- if (targetSdkVersion < 0) {
- // Invalid originating uid supplied. Abort install.
- Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
- return false;
- } else if (targetSdkVersion >= Build.VERSION_CODES.O
- && !isUidRequestingPermission(context.getPackageManager(), originatingUid,
- Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
- Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
- + Manifest.permission.REQUEST_INSTALL_PACKAGES);
- return false;
- }
- }
- return true;
- }
-
- /**
- * @param pm the {@link PackageManager} object
- * @param uid the UID of the caller who's permission is being checked
- * @param permission the permission name to check
- * @return {@code true} if the caller is requesting the said permission in its Manifest
- */
- public static boolean isUidRequestingPermission(PackageManager pm, int uid, String permission) {
- final String[] packageNames = pm.getPackagesForUid(uid);
- if (packageNames == null) {
- return false;
- }
- for (final String packageName : packageNames) {
- final PackageInfo packageInfo;
- try {
- packageInfo = pm.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore and try the next package
- continue;
- }
- if (packageInfo.requestedPermissions != null
- && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @param pi the {@link PackageInstaller} object to use
- * @param originatingUid the UID of the package performing a session based install
- * @param sessionId ID of the install session
- * @return {@code true} if the caller is the session owner
- */
- public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid,
- int sessionId) {
- if (originatingUid == Process.ROOT_UID) {
- return true;
- }
- PackageInstaller.SessionInfo sessionInfo = pi.getSessionInfo(sessionId);
- if (sessionInfo == null) {
- return false;
- }
- int installerUid = sessionInfo.getInstallerUid();
- return originatingUid == installerUid;
- }
-
- /**
- * Generates a stub {@link PackageInfo} object for the given packageName
- */
- public static PackageInfo generateStubPackageInfo(String packageName) {
- final PackageInfo info = new PackageInfo();
- final ApplicationInfo aInfo = new ApplicationInfo();
- info.applicationInfo = aInfo;
- info.packageName = info.applicationInfo.packageName = packageName;
- return info;
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * {@link SessionInfo} object
- */
- public static AppSnippet getAppSnippet(Context context, SessionInfo info) {
- PackageManager pm = context.getPackageManager();
- CharSequence label = info.getAppLabel();
- Drawable icon = info.getAppIcon() != null ?
- new BitmapDrawable(context.getResources(), info.getAppIcon())
- : pm.getDefaultActivityIcon();
- return new AppSnippet(label, icon);
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * {@link PackageInfo} object
- */
- public static AppSnippet getAppSnippet(Context context, PackageInfo pkgInfo) {
- return getAppSnippet(context, pkgInfo.applicationInfo);
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * {@link ApplicationInfo} object
- */
- public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo) {
- PackageManager pm = context.getPackageManager();
- CharSequence label = pm.getApplicationLabel(appInfo);
- Drawable icon = pm.getApplicationIcon(appInfo);
- return new AppSnippet(label, icon);
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * supplied APK file
- */
- public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo,
- File sourceFile) {
- ApplicationInfo appInfoFromFile = processAppInfoForFile(appInfo, sourceFile);
- CharSequence label = getAppLabelFromFile(context, appInfoFromFile);
- Drawable icon = getAppIconFromFile(context, appInfoFromFile);
- return new AppSnippet(label, icon);
- }
-
- /**
- * Utility method to load application label
- *
- * @param context context of package that can load the resources
- * @param appInfo ApplicationInfo object of package whose resources are to be loaded
- */
- public static CharSequence getAppLabelFromFile(Context context, ApplicationInfo appInfo) {
- PackageManager pm = context.getPackageManager();
- CharSequence label = null;
- // Try to load the label from the package's resources. If an app has not explicitly
- // specified any label, just use the package name.
- if (appInfo.labelRes != 0) {
- try {
- label = appInfo.loadLabel(pm);
- } catch (Resources.NotFoundException e) {
- }
- }
- if (label == null) {
- label = (appInfo.nonLocalizedLabel != null) ?
- appInfo.nonLocalizedLabel : appInfo.packageName;
- }
- return label;
- }
-
- /**
- * Utility method to load application icon
- *
- * @param context context of package that can load the resources
- * @param appInfo ApplicationInfo object of package whose resources are to be loaded
- */
- public static Drawable getAppIconFromFile(Context context, ApplicationInfo appInfo) {
- PackageManager pm = context.getPackageManager();
- Drawable icon = null;
- // Try to load the icon from the package's resources. If an app has not explicitly
- // specified any resource, just use the default icon for now.
- try {
- if (appInfo.icon != 0) {
- try {
- icon = appInfo.loadIcon(pm);
- } catch (Resources.NotFoundException e) {
- }
- }
- if (icon == null) {
- icon = context.getPackageManager().getDefaultActivityIcon();
- }
- } catch (OutOfMemoryError e) {
- Log.i(TAG, "Could not load app icon", e);
- }
- return icon;
- }
-
- private static ApplicationInfo processAppInfoForFile(ApplicationInfo appInfo, File sourceFile) {
- final String archiveFilePath = sourceFile.getAbsolutePath();
- appInfo.publicSourceDir = archiveFilePath;
-
- if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
- final File[] files = sourceFile.getParentFile().listFiles();
- final String[] splits = Arrays.stream(appInfo.splitNames)
- .map(i -> findFilePath(files, i + ".apk"))
- .filter(Objects::nonNull)
- .toArray(String[]::new);
-
- appInfo.splitSourceDirs = splits;
- appInfo.splitPublicSourceDirs = splits;
- }
- return appInfo;
- }
-
- private static String findFilePath(File[] files, String postfix) {
- for (File file : files) {
- final String path = file.getAbsolutePath();
- if (path.endsWith(postfix)) {
- return path;
- }
- }
- return null;
- }
-
- /**
- * @return the packageName corresponding to a UID.
- */
- public static String getPackageNameForUid(Context context, int sourceUid,
- String callingPackage) {
- if (sourceUid == Process.INVALID_UID) {
- return null;
- }
- // If the sourceUid belongs to the system downloads provider, we explicitly return the
- // name of the Download Manager package. This is because its UID is shared with multiple
- // packages, resulting in uncertainty about which package will end up first in the list
- // of packages associated with this UID
- PackageManager pm = context.getPackageManager();
- ApplicationInfo systemDownloadProviderInfo = getSystemDownloadsProviderInfo(
- pm, sourceUid);
- if (systemDownloadProviderInfo != null) {
- return systemDownloadProviderInfo.packageName;
- }
- String[] packagesForUid = pm.getPackagesForUid(sourceUid);
- if (packagesForUid == null) {
- return null;
- }
- if (packagesForUid.length > 1) {
- if (callingPackage != null) {
- for (String packageName : packagesForUid) {
- if (packageName.equals(callingPackage)) {
- return packageName;
- }
- }
- }
- Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
- }
- return packagesForUid[0];
- }
-
- /**
- * Utility method to get package information for a given {@link File}
- */
- public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
- String filePath = sourceFile.getAbsolutePath();
- if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
- File dir = sourceFile.getParentFile();
- if (dir.listFiles().length > 1) {
- // split apks, use file directory to get archive info
- filePath = dir.getPath();
- }
- }
- try {
- return context.getPackageManager().getPackageArchiveInfo(filePath, flags);
- } catch (Exception ignored) {
- return null;
- }
- }
-
- /**
- * Is a profile part of a user?
- *
- * @param userManager The user manager
- * @param userHandle The handle of the user
- * @param profileHandle The handle of the profile
- *
- * @return If the profile is part of the user or the profile parent of the user
- */
- public static boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
- UserHandle profileHandle) {
- if (userHandle.equals(profileHandle)) {
- return true;
- }
- return userManager.getProfileParent(profileHandle) != null
- && userManager.getProfileParent(profileHandle).equals(userHandle);
- }
-
- /**
- * The class to hold an incoming package's icon and label.
- * See {@link #getAppSnippet(Context, SessionInfo)},
- * {@link #getAppSnippet(Context, PackageInfo)},
- * {@link #getAppSnippet(Context, ApplicationInfo)},
- * {@link #getAppSnippet(Context, ApplicationInfo, File)}
- */
- public static class AppSnippet {
-
- private CharSequence mLabel;
- private Drawable mIcon;
-
- public AppSnippet(CharSequence label, Drawable icon) {
- mLabel = label;
- mIcon = icon;
- }
-
- public AppSnippet() {
- }
-
- public CharSequence getLabel() {
- return mLabel;
- }
-
- public void setLabel(CharSequence mLabel) {
- this.mLabel = mLabel;
- }
-
- public Drawable getIcon() {
- return mIcon;
- }
-
- public void setIcon(Drawable mIcon) {
- this.mIcon = mIcon;
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
new file mode 100644
index 0000000..8d8c2f1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -0,0 +1,440 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import java.io.File
+
+object PackageUtil {
+ private val LOG_TAG = InstallRepository::class.java.simpleName
+ private const val DOWNLOADS_AUTHORITY = "downloads"
+ private const val SPLIT_BASE_APK_END_WITH = "base.apk"
+
+ /**
+ * Determines if the UID belongs to the system downloads provider and returns the
+ * [ApplicationInfo] of the provider
+ *
+ * @param uid UID of the caller
+ * @return [ApplicationInfo] of the provider if a downloads provider exists, it is a
+ * system app, and its UID matches with the passed UID, null otherwise.
+ */
+ private fun getSystemDownloadsProviderInfo(pm: PackageManager, uid: Int): ApplicationInfo? {
+ // Check if there are currently enabled downloads provider on the system.
+ val providerInfo = pm.resolveContentProvider(DOWNLOADS_AUTHORITY, 0)
+ ?: return null
+ val appInfo = providerInfo.applicationInfo
+ return if ((appInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) && uid == appInfo.uid) {
+ appInfo
+ } else null
+ }
+
+ /**
+ * Get the maximum target sdk for a UID.
+ *
+ * @param context The context to use
+ * @param uid The UID requesting the install/uninstall
+ * @return The maximum target SDK or -1 if the uid does not match any packages.
+ */
+ @JvmStatic
+ fun getMaxTargetSdkVersionForUid(context: Context, uid: Int): Int {
+ val pm = context.packageManager
+ val packages = pm.getPackagesForUid(uid)
+ var targetSdkVersion = -1
+ if (packages != null) {
+ for (packageName in packages) {
+ try {
+ val info = pm.getApplicationInfo(packageName!!, 0)
+ targetSdkVersion = maxOf(targetSdkVersion, info.targetSdkVersion)
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Ignore and try the next package
+ }
+ }
+ }
+ return targetSdkVersion
+ }
+
+ @JvmStatic
+ fun canPackageQuery(context: Context, callingUid: Int, packageUri: Uri): Boolean {
+ val pm = context.packageManager
+ val info = pm.resolveContentProvider(
+ packageUri.authority!!,
+ PackageManager.ComponentInfoFlags.of(0)
+ ) ?: return false
+ val targetPackage = info.packageName
+ val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
+ for (callingPackage in callingPackages) {
+ try {
+ if (pm.canPackageQuery(callingPackage!!, targetPackage)) {
+ return true
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ // no-op
+ }
+ }
+ return false
+ }
+
+ /**
+ * @param context the [Context] object
+ * @param permission the permission name to check
+ * @param callingUid the UID of the caller who's permission is being checked
+ * @return `true` if the callingUid is granted the said permission
+ */
+ @JvmStatic
+ fun isPermissionGranted(context: Context, permission: String, callingUid: Int): Boolean {
+ return (context.checkPermission(permission, -1, callingUid)
+ == PackageManager.PERMISSION_GRANTED)
+ }
+
+ /**
+ * @param pm the [PackageManager] object
+ * @param permission the permission name to check
+ * @param packageName the name of the package who's permission is being checked
+ * @return `true` if the package is granted the said permission
+ */
+ @JvmStatic
+ fun isPermissionGranted(pm: PackageManager, permission: String, packageName: String): Boolean {
+ return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED
+ }
+
+ /**
+ * @param context the [Context] object
+ * @param callingUid the UID of the caller who's permission is being checked
+ * @param originatingUid the UID from where install is being originated. This could be same as
+ * callingUid or it will be the UID of the package performing a session based install
+ * @param isTrustedSource whether install request is coming from a privileged app or an app that
+ * has [Manifest.permission.INSTALL_PACKAGES] permission granted
+ * @return `true` if the package is granted the said permission
+ */
+ @JvmStatic
+ fun isInstallPermissionGrantedOrRequested(
+ context: Context,
+ callingUid: Int,
+ originatingUid: Int,
+ isTrustedSource: Boolean,
+ ): Boolean {
+ val isDocumentsManager =
+ isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid)
+ val isSystemDownloadsProvider =
+ getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
+
+ if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
+ val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+ if (targetSdkVersion < 0) {
+ // Invalid originating uid supplied. Abort install.
+ Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+ return false
+ } else if (targetSdkVersion >= Build.VERSION_CODES.O
+ && !isUidRequestingPermission(
+ context.packageManager, originatingUid,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES
+ )
+ ) {
+ Log.e(
+ LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ + Manifest.permission.REQUEST_INSTALL_PACKAGES
+ )
+ return false
+ }
+ }
+ return true
+ }
+
+ /**
+ * @param pm the [PackageManager] object
+ * @param uid the UID of the caller who's permission is being checked
+ * @param permission the permission name to check
+ * @return `true` if the caller is requesting the said permission in its Manifest
+ */
+ private fun isUidRequestingPermission(
+ pm: PackageManager,
+ uid: Int,
+ permission: String,
+ ): Boolean {
+ val packageNames = pm.getPackagesForUid(uid) ?: return false
+ for (packageName in packageNames) {
+ val packageInfo: PackageInfo = try {
+ pm.getPackageInfo(packageName!!, PackageManager.GET_PERMISSIONS)
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Ignore and try the next package
+ continue
+ }
+ if (packageInfo.requestedPermissions != null
+ && listOf(*packageInfo.requestedPermissions!!).contains(permission)
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * @param pi the [PackageInstaller] object to use
+ * @param originatingUid the UID of the package performing a session based install
+ * @param sessionId ID of the install session
+ * @return `true` if the caller is the session owner
+ */
+ @JvmStatic
+ fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
+ if (originatingUid == Process.ROOT_UID) {
+ return true
+ }
+ val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
+ val installerUid = sessionInfo.getInstallerUid()
+ return originatingUid == installerUid
+ }
+
+ /**
+ * Generates a stub [PackageInfo] object for the given packageName
+ */
+ @JvmStatic
+ fun generateStubPackageInfo(packageName: String?): PackageInfo {
+ val info = PackageInfo()
+ val aInfo = ApplicationInfo()
+ info.applicationInfo = aInfo
+ info.applicationInfo!!.packageName = packageName
+ info.packageName = info.applicationInfo!!.packageName
+ return info
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * [PackageInstaller.SessionInfo] object
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, info: PackageInstaller.SessionInfo): AppSnippet {
+ val pm = context.packageManager
+ val label = info.getAppLabel()
+ val icon = if (info.getAppIcon() != null) BitmapDrawable(
+ context.resources,
+ info.getAppIcon()
+ ) else pm.defaultActivityIcon
+ return AppSnippet(label, icon)
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * [PackageInfo] object
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, pkgInfo: PackageInfo): AppSnippet {
+ return pkgInfo.applicationInfo?.let { getAppSnippet(context, it) } ?: run {
+ AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+ }
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * [ApplicationInfo] object
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, appInfo: ApplicationInfo): AppSnippet {
+ val pm = context.packageManager
+ val label = pm.getApplicationLabel(appInfo)
+ val icon = pm.getApplicationIcon(appInfo)
+ return AppSnippet(label, icon)
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * supplied APK file
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, pkgInfo: PackageInfo, sourceFile: File): AppSnippet {
+ pkgInfo.applicationInfo?.let {
+ val appInfoFromFile = processAppInfoForFile(it, sourceFile)
+ val label = getAppLabelFromFile(context, appInfoFromFile)
+ val icon = getAppIconFromFile(context, appInfoFromFile)
+ return AppSnippet(label, icon)
+ } ?: run {
+ return AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+ }
+ }
+
+ /**
+ * Utility method to load application label
+ *
+ * @param context context of package that can load the resources
+ * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+ */
+ private fun getAppLabelFromFile(context: Context, appInfo: ApplicationInfo): CharSequence? {
+ val pm = context.packageManager
+ var label: CharSequence? = null
+ // Try to load the label from the package's resources. If an app has not explicitly
+ // specified any label, just use the package name.
+ if (appInfo.labelRes != 0) {
+ try {
+ label = appInfo.loadLabel(pm)
+ } catch (e: Resources.NotFoundException) {
+ }
+ }
+ if (label == null) {
+ label = if (appInfo.nonLocalizedLabel != null) appInfo.nonLocalizedLabel
+ else appInfo.packageName
+ }
+ return label
+ }
+
+ /**
+ * Utility method to load application icon
+ *
+ * @param context context of package that can load the resources
+ * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+ */
+ private fun getAppIconFromFile(context: Context, appInfo: ApplicationInfo): Drawable? {
+ val pm = context.packageManager
+ var icon: Drawable? = null
+ // Try to load the icon from the package's resources. If an app has not explicitly
+ // specified any resource, just use the default icon for now.
+ try {
+ if (appInfo.icon != 0) {
+ try {
+ icon = appInfo.loadIcon(pm)
+ } catch (e: Resources.NotFoundException) {
+ }
+ }
+ if (icon == null) {
+ icon = context.packageManager.defaultActivityIcon
+ }
+ } catch (e: OutOfMemoryError) {
+ Log.i(LOG_TAG, "Could not load app icon", e)
+ }
+ return icon
+ }
+
+ private fun processAppInfoForFile(appInfo: ApplicationInfo, sourceFile: File): ApplicationInfo {
+ val archiveFilePath = sourceFile.absolutePath
+ appInfo.publicSourceDir = archiveFilePath
+ if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
+ val files = sourceFile.parentFile?.listFiles()
+ val splits = appInfo.splitNames!!
+ .mapNotNull { findFilePath(files, "$it.apk") }
+ .toTypedArray()
+
+ appInfo.splitSourceDirs = splits
+ appInfo.splitPublicSourceDirs = splits
+ }
+ return appInfo
+ }
+
+ private fun findFilePath(files: Array<File>?, postfix: String): String? {
+ files?.let {
+ for (file in it) {
+ val path = file.absolutePath
+ if (path.endsWith(postfix)) {
+ return path
+ }
+ }
+ }
+ return null
+ }
+
+ /**
+ * @return the packageName corresponding to a UID.
+ */
+ @JvmStatic
+ fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
+ if (sourceUid == Process.INVALID_UID) {
+ return null
+ }
+ // If the sourceUid belongs to the system downloads provider, we explicitly return the
+ // name of the Download Manager package. This is because its UID is shared with multiple
+ // packages, resulting in uncertainty about which package will end up first in the list
+ // of packages associated with this UID
+ val pm = context.packageManager
+ val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+ if (systemDownloadProviderInfo != null) {
+ return systemDownloadProviderInfo.packageName
+ }
+ val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+ if (packagesForUid.size > 1) {
+ if (callingPackage != null) {
+ for (packageName in packagesForUid) {
+ if (packageName == callingPackage) {
+ return packageName
+ }
+ }
+ }
+ Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
+ }
+ return packagesForUid[0]
+ }
+
+ /**
+ * Utility method to get package information for a given [File]
+ */
+ @JvmStatic
+ fun getPackageInfo(context: Context, sourceFile: File, flags: Int): PackageInfo? {
+ var filePath = sourceFile.absolutePath
+ if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
+ val dir = sourceFile.parentFile
+ if ((dir?.listFiles()?.size ?: 0) > 1) {
+ // split apks, use file directory to get archive info
+ filePath = dir.path
+ }
+ }
+ return try {
+ context.packageManager.getPackageArchiveInfo(filePath, flags)
+ } catch (ignored: Exception) {
+ null
+ }
+ }
+
+ /**
+ * Is a profile part of a user?
+ *
+ * @param userManager The user manager
+ * @param userHandle The handle of the user
+ * @param profileHandle The handle of the profile
+ *
+ * @return If the profile is part of the user or the profile parent of the user
+ */
+ @JvmStatic
+ fun isProfileOfOrSame(
+ userManager: UserManager,
+ userHandle: UserHandle,
+ profileHandle: UserHandle?,
+ ): Boolean {
+ if (profileHandle == null) {
+ return false
+ }
+ return if (userHandle == profileHandle) {
+ true
+ } else userManager.getProfileParent(profileHandle) != null
+ && userManager.getProfileParent(profileHandle) == userHandle
+ }
+
+ /**
+ * The class to hold an incoming package's icon and label.
+ * See [getAppSnippet]
+ */
+ data class AppSnippet(var label: CharSequence?, var icon: Drawable?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
deleted file mode 100644
index a2c81f1..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
+++ /dev/null
@@ -1,126 +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.packageinstaller.v2.model;
-
-import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> {
-
- private static final String TAG = SessionStager.class.getSimpleName();
- private final Context mContext;
- private final Uri mUri;
- private final int mStagedSessionId;
- private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0);
- private final SessionStageListener mListener;
-
- SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) {
- mContext = context;
- mUri = uri;
- mStagedSessionId = stagedSessionId;
- mListener = listener;
- }
-
- @Override
- protected PackageInstaller.SessionInfo doInBackground(Void... params) {
- PackageInstaller pi = mContext.getPackageManager().getPackageInstaller();
- try (PackageInstaller.Session session = pi.openSession(mStagedSessionId);
- InputStream in = mContext.getContentResolver().openInputStream(mUri)) {
- session.setStagingProgress(0);
-
- if (in == null) {
- return null;
- }
- final long sizeBytes = getContentSizeBytes();
- mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1);
-
- long totalRead = 0;
- try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
- byte[] buffer = new byte[1024 * 1024];
- while (true) {
- int numRead = in.read(buffer);
-
- if (numRead == -1) {
- session.fsync(out);
- break;
- }
-
- if (isCancelled()) {
- break;
- }
-
- out.write(buffer, 0, numRead);
- if (sizeBytes > 0) {
- totalRead += numRead;
- float fraction = ((float) totalRead / (float) sizeBytes);
- session.setStagingProgress(fraction);
- publishProgress((int) (fraction * 100.0));
- }
- }
- }
- return pi.getSessionInfo(mStagedSessionId);
- } catch (IOException | SecurityException | IllegalStateException
- | IllegalArgumentException e) {
- Log.w(TAG, "Error staging apk from content URI", e);
- return null;
- }
- }
-
- private long getContentSizeBytes() {
- try (AssetFileDescriptor afd = mContext.getContentResolver()
- .openAssetFileDescriptor(mUri, "r")) {
- return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
- } catch (IOException e) {
- Log.w(TAG, "Failed to open asset file descriptor", e);
- return UNKNOWN_LENGTH;
- }
- }
-
- public MutableLiveData<Integer> getProgress() {
- return mProgressLiveData;
- }
-
- @Override
- protected void onProgressUpdate(Integer... progress) {
- if (progress != null && progress.length > 0) {
- mProgressLiveData.setValue(progress[0]);
- }
- }
-
- @Override
- protected void onPostExecute(SessionInfo sessionInfo) {
- if (sessionInfo == null || !sessionInfo.isActive()
- || sessionInfo.getResolvedBaseApkPath() == null) {
- Log.w(TAG, "Session info is invalid: " + sessionInfo);
- mListener.onStagingFailure();
- return;
- }
- mListener.onStagingSuccess(sessionInfo);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
new file mode 100644
index 0000000..c9bfa17
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.content.Context
+import android.content.pm.PackageInstaller
+import android.content.res.AssetFileDescriptor
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import java.io.IOException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class SessionStager internal constructor(
+ private val context: Context,
+ private val uri: Uri,
+ private val stagedSessionId: Int
+) {
+
+ companion object {
+ private val LOG_TAG = SessionStager::class.java.simpleName
+ }
+
+ private val _progress = MutableLiveData(0)
+ val progress: LiveData<Int>
+ get() = _progress
+
+ suspend fun execute(): Boolean = withContext(Dispatchers.IO) {
+ val pi: PackageInstaller = context.packageManager.packageInstaller
+ var sessionInfo: PackageInstaller.SessionInfo?
+ try {
+ val session = pi.openSession(stagedSessionId)
+ context.contentResolver.openInputStream(uri).use { instream ->
+ session.setStagingProgress(0f)
+
+ if (instream == null) {
+ return@withContext false
+ }
+
+ val sizeBytes = getContentSizeBytes()
+ publishProgress(if (sizeBytes > 0) 0 else -1)
+
+ var totalRead: Long = 0
+ session.openWrite("PackageInstaller", 0, sizeBytes).use { out ->
+ val buffer = ByteArray(1024 * 1024)
+ while (true) {
+ val numRead = instream.read(buffer)
+ if (numRead == -1) {
+ session.fsync(out)
+ break
+ }
+ out.write(buffer, 0, numRead)
+
+ if (sizeBytes > 0) {
+ totalRead += numRead.toLong()
+ val fraction = totalRead.toFloat() / sizeBytes.toFloat()
+ session.setStagingProgress(fraction)
+ publishProgress((fraction * 100.0).toInt())
+ }
+ }
+ }
+ sessionInfo = pi.getSessionInfo(stagedSessionId)
+ }
+ } catch (e: Exception) {
+ Log.w(LOG_TAG, "Error staging apk from content URI", e)
+ sessionInfo = null
+ }
+
+ return@withContext if (sessionInfo == null
+ || !sessionInfo?.isActive!!
+ || sessionInfo?.resolvedBaseApkPath == null
+ ) {
+ Log.w(LOG_TAG, "Session info is invalid: $sessionInfo")
+ false
+ } else {
+ true
+ }
+ }
+
+ private fun getContentSizeBytes(): Long {
+ return try {
+ context.contentResolver
+ .openAssetFileDescriptor(uri, "r")
+ .use { afd -> afd?.length ?: AssetFileDescriptor.UNKNOWN_LENGTH }
+ } catch (e: IOException) {
+ Log.w(LOG_TAG, "Failed to open asset file descriptor", e)
+ AssetFileDescriptor.UNKNOWN_LENGTH
+ }
+ }
+
+ private suspend fun publishProgress(progressValue: Int) = withContext(Dispatchers.Main) {
+ _progress.value = progressValue
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
deleted file mode 100644
index a07c532..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
+++ /dev/null
@@ -1,716 +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
- *
- * https://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.packageinstaller.v2.model;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
-import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
-import static com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_APP_UNAVAILABLE;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_GENERIC_ERROR;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.app.usage.StorageStats;
-import android.app.usage.StorageStatsManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.UninstallCompleteCallback;
-import android.content.pm.VersionedPackage;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import java.io.IOException;
-import java.util.List;
-
-public class UninstallRepository {
-
- private static final String TAG = UninstallRepository.class.getSimpleName();
- private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall_failure";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
- private static final String EXTRA_UNINSTALL_ID =
- "com.android.packageinstaller.extra.UNINSTALL_ID";
- private static final String EXTRA_APP_LABEL =
- "com.android.packageinstaller.extra.APP_LABEL";
- private static final String EXTRA_IS_CLONE_APP =
- "com.android.packageinstaller.extra.IS_CLONE_APP";
- private static final String EXTRA_PACKAGE_NAME =
- "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME";
-
- private final Context mContext;
- private final AppOpsManager mAppOpsManager;
- private final PackageManager mPackageManager;
- private final UserManager mUserManager;
- private final NotificationManager mNotificationManager;
- private final MutableLiveData<UninstallStage> mUninstallResult = new MutableLiveData<>();
- public UserHandle mUninstalledUser;
- public UninstallCompleteCallback mCallback;
- private ApplicationInfo mTargetAppInfo;
- private ActivityInfo mTargetActivityInfo;
- private Intent mIntent;
- private CharSequence mTargetAppLabel;
- private String mTargetPackageName;
- private String mCallingActivity;
- private boolean mUninstallFromAllUsers;
- private boolean mIsClonedApp;
- private int mUninstallId;
-
- public UninstallRepository(Context context) {
- mContext = context;
- mAppOpsManager = context.getSystemService(AppOpsManager.class);
- mPackageManager = context.getPackageManager();
- mUserManager = context.getSystemService(UserManager.class);
- mNotificationManager = context.getSystemService(NotificationManager.class);
- }
-
- public UninstallStage performPreUninstallChecks(Intent intent, CallerInfo callerInfo) {
- mIntent = intent;
-
- int callingUid = callerInfo.getUid();
- mCallingActivity = callerInfo.getActivityName();
-
- if (callingUid == Process.INVALID_UID) {
- Log.e(TAG, "Could not determine the launching uid.");
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- // TODO: should we give any indication to the user?
- }
-
- String callingPackage = getPackageNameForUid(mContext, callingUid, null);
- if (callingPackage == null) {
- Log.e(TAG, "Package not found for originating uid " + callingUid);
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- } else {
- if (mAppOpsManager.noteOpNoThrow(
- AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
- != MODE_ALLOWED) {
- Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- }
- }
-
- if (getMaxTargetSdkVersionForUid(mContext, callingUid) >= Build.VERSION_CODES.P
- && !isPermissionGranted(mContext, Manifest.permission.REQUEST_DELETE_PACKAGES,
- callingUid)
- && !isPermissionGranted(mContext, Manifest.permission.DELETE_PACKAGES, callingUid)) {
- Log.e(TAG, "Uid " + callingUid + " does not have "
- + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
- + Manifest.permission.DELETE_PACKAGES);
-
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- }
-
- // Get intent information.
- // We expect an intent with URI of the form package:<packageName>#<className>
- // className is optional; if specified, it is the activity the user chose to uninstall
- final Uri packageUri = intent.getData();
- if (packageUri == null) {
- Log.e(TAG, "No package URI in intent");
- return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
- }
- mTargetPackageName = packageUri.getEncodedSchemeSpecificPart();
- if (mTargetPackageName == null) {
- Log.e(TAG, "Invalid package name in URI: " + packageUri);
- return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
- }
-
- mUninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
- false);
- if (mUninstallFromAllUsers && !mUserManager.isAdminUser()) {
- Log.e(TAG, "Only admin user can request uninstall for all users");
- return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
- }
-
- mUninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
- if (mUninstalledUser == null) {
- mUninstalledUser = Process.myUserHandle();
- } else {
- List<UserHandle> profiles = mUserManager.getUserProfiles();
- if (!profiles.contains(mUninstalledUser)) {
- Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
- + "for user " + mUninstalledUser);
- return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
- }
- }
-
- mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
- PackageManager.UninstallCompleteCallback.class);
-
- try {
- mTargetAppInfo = mPackageManager.getApplicationInfo(mTargetPackageName,
- PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to get packageName");
- }
-
- if (mTargetAppInfo == null) {
- Log.e(TAG, "Invalid packageName: " + mTargetPackageName);
- return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
- }
-
- // The class name may have been specified (e.g. when deleting an app from all apps)
- final String className = packageUri.getFragment();
- if (className != null) {
- try {
- mTargetActivityInfo = mPackageManager.getActivityInfo(
- new ComponentName(mTargetPackageName, className),
- PackageManager.ComponentInfoFlags.of(0));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to get className");
- // Continue as the ActivityInfo isn't critical.
- }
- }
-
- return new UninstallReady();
- }
-
- public UninstallStage generateUninstallDetails() {
- UninstallUserActionRequired.Builder uarBuilder = new UninstallUserActionRequired.Builder();
- StringBuilder messageBuilder = new StringBuilder();
-
- mTargetAppLabel = mTargetAppInfo.loadSafeLabel(mPackageManager);
-
- // If the Activity label differs from the App label, then make sure the user
- // knows the Activity belongs to the App being uninstalled.
- if (mTargetActivityInfo != null) {
- final CharSequence activityLabel = mTargetActivityInfo.loadSafeLabel(mPackageManager);
- if (CharSequence.compare(activityLabel, mTargetAppLabel) != 0) {
- messageBuilder.append(
- mContext.getString(R.string.uninstall_activity_text, activityLabel));
- messageBuilder.append(" ").append(mTargetAppLabel).append(".\n\n");
- }
- }
-
- final boolean isUpdate =
- (mTargetAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- final UserHandle myUserHandle = Process.myUserHandle();
- boolean isSingleUser = isSingleUser();
-
- if (isUpdate) {
- messageBuilder.append(mContext.getString(
- isSingleUser ? R.string.uninstall_update_text :
- R.string.uninstall_update_text_multiuser));
- } else if (mUninstallFromAllUsers && !isSingleUser) {
- messageBuilder.append(mContext.getString(
- R.string.uninstall_application_text_all_users));
- } else if (!mUninstalledUser.equals(myUserHandle)) {
- // Uninstalling user is issuing uninstall for another user
- UserManager customUserManager = mContext.createContextAsUser(mUninstalledUser, 0)
- .getSystemService(UserManager.class);
- String userName = customUserManager.getUserName();
-
- String uninstalledUserType = getUninstalledUserType(myUserHandle, mUninstalledUser);
- String messageString;
- if (USER_TYPE_PROFILE_MANAGED.equals(uninstalledUserType)) {
- messageString = mContext.getString(
- R.string.uninstall_application_text_current_user_work_profile, userName);
- } else if (USER_TYPE_PROFILE_CLONE.equals(uninstalledUserType)) {
- mIsClonedApp = true;
- messageString = mContext.getString(
- R.string.uninstall_application_text_current_user_clone_profile);
- } else {
- messageString = mContext.getString(
- R.string.uninstall_application_text_user, userName);
- }
- messageBuilder.append(messageString);
- } else if (isCloneProfile(mUninstalledUser)) {
- mIsClonedApp = true;
- messageBuilder.append(mContext.getString(
- R.string.uninstall_application_text_current_user_clone_profile));
- } else if (myUserHandle.equals(UserHandle.SYSTEM)
- && hasClonedInstance(mTargetAppInfo.packageName)) {
- messageBuilder.append(mContext.getString(
- R.string.uninstall_application_text_with_clone_instance, mTargetAppLabel));
- } else {
- messageBuilder.append(mContext.getString(R.string.uninstall_application_text));
- }
-
- uarBuilder.setMessage(messageBuilder.toString());
-
- if (mIsClonedApp) {
- uarBuilder.setTitle(mContext.getString(R.string.cloned_app_label, mTargetAppLabel));
- } else {
- uarBuilder.setTitle(mTargetAppLabel.toString());
- }
-
- boolean suggestToKeepAppData = false;
- try {
- PackageInfo pkgInfo = mPackageManager.getPackageInfo(mTargetPackageName, 0);
- suggestToKeepAppData =
- pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.hasFragileUserData();
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Cannot check hasFragileUserData for " + mTargetPackageName, e);
- }
-
- long appDataSize = 0;
- if (suggestToKeepAppData) {
- appDataSize = getAppDataSize(mTargetPackageName,
- mUninstallFromAllUsers ? null : mUninstalledUser);
- }
- uarBuilder.setAppDataSize(appDataSize);
-
- return uarBuilder.build();
- }
-
- /**
- * Returns whether there is only one "full" user on this device.
- *
- * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
- * headless system user mode}, the system user is not "full", so it's not be considered in the
- * calculation.</p>
- */
- private boolean isSingleUser() {
- final int userCount = mUserManager.getUserCount();
- return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
- }
-
- /**
- * Returns the type of the user from where an app is being uninstalled. We are concerned with
- * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
- * belong to the same profile group.
- */
- @Nullable
- private String getUninstalledUserType(UserHandle myUserHandle,
- UserHandle uninstalledUserHandle) {
- if (!mUserManager.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
- return null;
- }
-
- UserManager customUserManager = mContext.createContextAsUser(uninstalledUserHandle, 0)
- .getSystemService(UserManager.class);
- String[] userTypes = {USER_TYPE_PROFILE_MANAGED, USER_TYPE_PROFILE_CLONE};
- for (String userType : userTypes) {
- if (customUserManager.isUserOfType(userType)) {
- return userType;
- }
- }
- return null;
- }
-
- private boolean hasClonedInstance(String packageName) {
- // Check if clone user is present on the device.
- UserHandle cloneUser = null;
- List<UserHandle> profiles = mUserManager.getUserProfiles();
- for (UserHandle userHandle : profiles) {
- if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
- cloneUser = userHandle;
- break;
- }
- }
- // Check if another instance of given package exists in clone user profile.
- try {
- return cloneUser != null
- && mPackageManager.getPackageUidAsUser(packageName,
- PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private boolean isCloneProfile(UserHandle userHandle) {
- UserManager customUserManager = mContext.createContextAsUser(userHandle, 0)
- .getSystemService(UserManager.class);
- return customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE);
- }
-
- /**
- * Get number of bytes of the app data of the package.
- *
- * @param pkg The package that might have app data.
- * @param user The user the package belongs to or {@code null} if files of all users should
- * be counted.
- * @return The number of bytes.
- */
- private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
- if (user != null) {
- return getAppDataSizeForUser(pkg, user);
- }
- // We are uninstalling from all users. Get cumulative app data size for all users.
- List<UserHandle> userHandles = mUserManager.getUserHandles(true);
- long totalAppDataSize = 0;
- int numUsers = userHandles.size();
- for (int i = 0; i < numUsers; i++) {
- totalAppDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
- }
- return totalAppDataSize;
- }
-
- /**
- * Get number of bytes of the app data of the package.
- *
- * @param pkg The package that might have app data.
- * @param user The user the package belongs to
- * @return The number of bytes.
- */
- private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
- StorageStatsManager storageStatsManager =
- mContext.getSystemService(StorageStatsManager.class);
- try {
- StorageStats stats = storageStatsManager.queryStatsForPackage(
- mPackageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user);
- return stats.getDataBytes();
- } catch (PackageManager.NameNotFoundException | IOException | SecurityException e) {
- Log.e(TAG, "Cannot determine amount of app data for " + pkg, e);
- }
- return 0;
- }
-
- public void initiateUninstall(boolean keepData) {
- // Get an uninstallId to track results and show a notification on non-TV devices.
- try {
- mUninstallId = UninstallEventReceiver.addObserver(mContext,
- EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult);
- } catch (EventResultPersister.OutOfIdsException e) {
- Log.e(TAG, "Failed to start uninstall", e);
- handleUninstallResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
- return;
- }
-
- // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
- mUninstallResult.setValue(new UninstallUninstalling(mTargetAppLabel, mIsClonedApp));
-
- Bundle uninstallData = new Bundle();
- uninstallData.putInt(EXTRA_UNINSTALL_ID, mUninstallId);
- uninstallData.putString(EXTRA_PACKAGE_NAME, mTargetPackageName);
- uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, mUninstallFromAllUsers);
- uninstallData.putCharSequence(EXTRA_APP_LABEL, mTargetAppLabel);
- uninstallData.putBoolean(EXTRA_IS_CLONE_APP, mIsClonedApp);
- Log.i(TAG, "Uninstalling extras = " + uninstallData);
-
- // Get a PendingIntent for result broadcast and issue an uninstall request
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
- broadcastIntent.setPackage(mContext.getPackageName());
-
- PendingIntent pendingIntent =
- PendingIntent.getBroadcast(mContext, mUninstallId, broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
- if (!startUninstall(mTargetPackageName, mUninstalledUser, pendingIntent,
- mUninstallFromAllUsers, keepData)) {
- handleUninstallResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
- }
- }
-
- private void handleUninstallResult(int status, int legacyStatus, @Nullable String message,
- int serviceId) {
- if (mCallback != null) {
- // The caller will be informed about the result via a callback
- mCallback.onUninstallComplete(mTargetPackageName, legacyStatus, message);
-
- // Since the caller already received the results, just finish the app at this point
- mUninstallResult.setValue(null);
- return;
- }
-
- boolean returnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
- if (returnResult || mCallingActivity != null) {
- Intent intent = new Intent();
- intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
-
- if (status == PackageInstaller.STATUS_SUCCESS) {
- UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
- .setResultIntent(intent)
- .setActivityResultCode(Activity.RESULT_OK);
- mUninstallResult.setValue(successBuilder.build());
- } else {
- UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(true)
- .setResultIntent(intent)
- .setActivityResultCode(Activity.RESULT_FIRST_USER);
- mUninstallResult.setValue(failedBuilder.build());
- }
- return;
- }
-
- // Caller did not want the result back. So, we either show a Toast, or a Notification.
- if (status == PackageInstaller.STATUS_SUCCESS) {
- UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
- .setActivityResultCode(legacyStatus)
- .setMessage(mIsClonedApp
- ? mContext.getString(R.string.uninstall_done_clone_app, mTargetAppLabel)
- : mContext.getString(R.string.uninstall_done_app, mTargetAppLabel));
- mUninstallResult.setValue(successBuilder.build());
- } else {
- UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(false);
- Notification.Builder uninstallFailedNotification = null;
-
- NotificationChannel uninstallFailureChannel = new NotificationChannel(
- UNINSTALL_FAILURE_CHANNEL,
- mContext.getString(R.string.uninstall_failure_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT);
- mNotificationManager.createNotificationChannel(uninstallFailureChannel);
-
- uninstallFailedNotification = new Notification.Builder(mContext,
- UNINSTALL_FAILURE_CHANNEL);
-
- UserHandle myUserHandle = Process.myUserHandle();
- switch (legacyStatus) {
- case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
- // Find out if the package is an active admin for some non-current user.
- UserHandle otherBlockingUserHandle =
- findUserOfDeviceAdmin(myUserHandle, mTargetPackageName);
-
- if (otherBlockingUserHandle == null) {
- Log.d(TAG, "Uninstall failed because " + mTargetPackageName
- + " is a device admin");
-
- addDeviceManagerButton(mContext, uninstallFailedNotification);
- setBigText(uninstallFailedNotification, mContext.getString(
- R.string.uninstall_failed_device_policy_manager));
- } else {
- Log.d(TAG, "Uninstall failed because " + mTargetPackageName
- + " is a device admin of user " + otherBlockingUserHandle);
-
- String userName =
- mContext.createContextAsUser(otherBlockingUserHandle, 0)
- .getSystemService(UserManager.class).getUserName();
- setBigText(uninstallFailedNotification, String.format(
- mContext.getString(
- R.string.uninstall_failed_device_policy_manager_of_user),
- userName));
- }
- }
- case PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
- UserHandle otherBlockingUserHandle = findBlockingUser(mTargetPackageName);
- boolean isProfileOfOrSame = isProfileOfOrSame(mUserManager, myUserHandle,
- otherBlockingUserHandle);
-
- if (isProfileOfOrSame) {
- addDeviceManagerButton(mContext, uninstallFailedNotification);
- } else {
- addManageUsersButton(mContext, uninstallFailedNotification);
- }
-
- String bigText = null;
- if (otherBlockingUserHandle == null) {
- Log.d(TAG, "Uninstall failed for " + mTargetPackageName +
- " with code " + status + " no blocking user");
- } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
- bigText = mContext.getString(
- R.string.uninstall_blocked_device_owner);
- } else {
- bigText = mContext.getString(mUninstallFromAllUsers ?
- R.string.uninstall_all_blocked_profile_owner
- : R.string.uninstall_blocked_profile_owner);
- }
- if (bigText != null) {
- setBigText(uninstallFailedNotification, bigText);
- }
- }
- default -> {
- Log.d(TAG, "Uninstall blocked for " + mTargetPackageName
- + " with legacy code " + legacyStatus);
- }
- }
-
- uninstallFailedNotification.setContentTitle(
- mContext.getString(R.string.uninstall_failed_app, mTargetAppLabel));
- uninstallFailedNotification.setOngoing(false);
- uninstallFailedNotification.setSmallIcon(R.drawable.ic_error);
- failedBuilder.setUninstallNotification(mUninstallId,
- uninstallFailedNotification.build());
-
- mUninstallResult.setValue(failedBuilder.build());
- }
- }
-
- /**
- * @param myUserHandle {@link UserHandle} of the current user.
- * @param packageName Name of the package being uninstalled.
- * @return the {@link UserHandle} of the user in which a package is a device admin.
- */
- @Nullable
- private UserHandle findUserOfDeviceAdmin(UserHandle myUserHandle, String packageName) {
- for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
- // We only catch the case when the user in question is neither the
- // current user nor its profile.
- if (isProfileOfOrSame(mUserManager, myUserHandle, otherUserHandle)) {
- continue;
- }
- DevicePolicyManager dpm = mContext.createContextAsUser(otherUserHandle, 0)
- .getSystemService(DevicePolicyManager.class);
- if (dpm.packageHasActiveAdmins(packageName)) {
- return otherUserHandle;
- }
- }
- return null;
- }
-
- /**
- *
- * @param packageName Name of the package being uninstalled.
- * @return {@link UserHandle} of the user in which a package is blocked from being uninstalled.
- */
- @Nullable
- private UserHandle findBlockingUser(String packageName) {
- for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
- // TODO (b/307399586): Add a negation when the logic of the method
- // is fixed
- if (mPackageManager.canUserUninstall(packageName, otherUserHandle)) {
- return otherUserHandle;
- }
- }
- return null;
- }
-
- /**
- * Set big text for the notification.
- *
- * @param builder The builder of the notification
- * @param text The text to set.
- */
- private void setBigText(@NonNull Notification.Builder builder,
- @NonNull CharSequence text) {
- builder.setStyle(new Notification.BigTextStyle().bigText(text));
- }
-
- /**
- * Add a button to the notification that links to the user management.
- *
- * @param context The context the notification is created in
- * @param builder The builder of the notification
- */
- private void addManageUsersButton(@NonNull Context context,
- @NonNull Notification.Builder builder) {
- builder.addAction((new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
- context.getString(R.string.manage_users),
- PendingIntent.getActivity(context, 0, getUserSettingsIntent(),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
- }
-
- private Intent getUserSettingsIntent() {
- Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- /**
- * Add a button to the notification that links to the device policy management.
- *
- * @param context The context the notification is created in
- * @param builder The builder of the notification
- */
- private void addDeviceManagerButton(@NonNull Context context,
- @NonNull Notification.Builder builder) {
- builder.addAction((new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_lock),
- context.getString(R.string.manage_device_administrators),
- PendingIntent.getActivity(context, 0, getDeviceManagerIntent(),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
- }
-
- private Intent getDeviceManagerIntent() {
- Intent intent = new Intent();
- intent.setClassName("com.android.settings",
- "com.android.settings.Settings$DeviceAdminSettingsActivity");
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- /**
- * Starts an uninstall for the given package.
- *
- * @return {@code true} if there was no exception while uninstalling. This does not represent
- * the result of the uninstall. Result will be made available in
- * {@link #handleUninstallResult(int, int, String, int)}
- */
- private boolean startUninstall(String packageName, UserHandle targetUser,
- PendingIntent pendingIntent, boolean uninstallFromAllUsers, boolean keepData) {
- int flags = uninstallFromAllUsers ? PackageManager.DELETE_ALL_USERS : 0;
- flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
- try {
- mContext.createContextAsUser(targetUser, 0)
- .getPackageManager().getPackageInstaller().uninstall(
- new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
- flags, pendingIntent.getIntentSender());
- return true;
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Failed to uninstall", e);
- return false;
- }
- }
-
- public void cancelInstall() {
- if (mCallback != null) {
- mCallback.onUninstallComplete(mTargetPackageName,
- PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
- }
- }
-
- public MutableLiveData<UninstallStage> getUninstallResult() {
- return mUninstallResult;
- }
-
- public static class CallerInfo {
-
- private final String mActivityName;
- private final int mUid;
-
- public CallerInfo(String activityName, int uid) {
- mActivityName = activityName;
- mUid = uid;
- }
-
- public String getActivityName() {
- return mActivityName;
- }
-
- public int getUid() {
- return mUid;
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
new file mode 100644
index 0000000..7cc95c5
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -0,0 +1,739 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.app.usage.StorageStatsManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.pm.VersionedPackage
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.UninstallEventReceiver
+import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame
+
+class UninstallRepository(private val context: Context) {
+
+ private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+ private val packageManager: PackageManager = context.packageManager
+ private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+ private val notificationManager: NotificationManager? =
+ context.getSystemService(NotificationManager::class.java)
+ val uninstallResult = MutableLiveData<UninstallStage?>()
+ private var uninstalledUser: UserHandle? = null
+ private var callback: PackageManager.UninstallCompleteCallback? = null
+ private var targetAppInfo: ApplicationInfo? = null
+ private var targetActivityInfo: ActivityInfo? = null
+ private lateinit var intent: Intent
+ private lateinit var targetAppLabel: CharSequence
+ private var targetPackageName: String? = null
+ private var callingActivity: String? = null
+ private var uninstallFromAllUsers = false
+ private var isClonedApp = false
+ private var uninstallId = 0
+
+ fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage {
+ this.intent = intent
+
+ val callingUid = callerInfo.uid
+ callingActivity = callerInfo.activityName
+
+ if (callingUid == Process.INVALID_UID) {
+ Log.e(LOG_TAG, "Could not determine the launching uid.")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ // TODO: should we give any indication to the user?
+ }
+
+ val callingPackage = getPackageNameForUid(context, callingUid, null)
+ if (callingPackage == null) {
+ Log.e(LOG_TAG, "Package not found for originating uid $callingUid")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ } else {
+ if (appOpsManager!!.noteOpNoThrow(
+ AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage
+ ) != AppOpsManager.MODE_ALLOWED
+ ) {
+ Log.e(LOG_TAG, "Install from uid $callingUid disallowed by AppOps")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ }
+ }
+
+ if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P
+ && !isPermissionGranted(
+ context, Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid
+ )
+ && !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
+ ) {
+ Log.e(
+ LOG_TAG, "Uid " + callingUid + " does not have "
+ + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+ + Manifest.permission.DELETE_PACKAGES
+ )
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ }
+
+ // Get intent information.
+ // We expect an intent with URI of the form package:<packageName>#<className>
+ // className is optional; if specified, it is the activity the user chose to uninstall
+ val packageUri = intent.data
+ if (packageUri == null) {
+ Log.e(LOG_TAG, "No package URI in intent")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+ }
+ targetPackageName = packageUri.encodedSchemeSpecificPart
+ if (targetPackageName == null) {
+ Log.e(LOG_TAG, "Invalid package name in URI: $packageUri")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+ }
+
+ uninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false)
+ if (uninstallFromAllUsers && !userManager!!.isAdminUser) {
+ Log.e(LOG_TAG, "Only admin user can request uninstall for all users")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+ }
+
+ uninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
+ if (uninstalledUser == null) {
+ uninstalledUser = Process.myUserHandle()
+ } else {
+ val profiles = userManager!!.userProfiles
+ if (!profiles.contains(uninstalledUser)) {
+ Log.e(
+ LOG_TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+ + "for user " + uninstalledUser
+ )
+ return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+ }
+ }
+
+ callback = intent.getParcelableExtra(
+ PackageInstaller.EXTRA_CALLBACK, PackageManager.UninstallCompleteCallback::class.java
+ )
+
+ try {
+ targetAppInfo = packageManager.getApplicationInfo(
+ targetPackageName!!,
+ PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong())
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Unable to get packageName")
+ }
+
+ if (targetAppInfo == null) {
+ Log.e(LOG_TAG, "Invalid packageName: $targetPackageName")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+ }
+
+ // The class name may have been specified (e.g. when deleting an app from all apps)
+ val className = packageUri.fragment
+ if (className != null) {
+ try {
+ targetActivityInfo = packageManager.getActivityInfo(
+ ComponentName(targetPackageName!!, className),
+ PackageManager.ComponentInfoFlags.of(0)
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Unable to get className")
+ // Continue as the ActivityInfo isn't critical.
+ }
+ }
+
+ return UninstallReady()
+ }
+
+ fun generateUninstallDetails(): UninstallStage {
+ val messageBuilder = StringBuilder()
+
+ targetAppLabel = targetAppInfo!!.loadSafeLabel(packageManager)
+
+ // If the Activity label differs from the App label, then make sure the user
+ // knows the Activity belongs to the App being uninstalled.
+ if (targetActivityInfo != null) {
+ val activityLabel = targetActivityInfo!!.loadSafeLabel(packageManager)
+ if (!activityLabel.contentEquals(targetAppLabel)) {
+ messageBuilder.append(
+ context.getString(R.string.uninstall_activity_text, activityLabel)
+ )
+ messageBuilder.append(" ").append(targetAppLabel).append(".\n\n")
+ }
+ }
+
+ val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+ val myUserHandle = Process.myUserHandle()
+ val isSingleUser = isSingleUser()
+
+ if (isUpdate) {
+ messageBuilder.append(context.getString(
+ if (isSingleUser) R.string.uninstall_update_text
+ else R.string.uninstall_update_text_multiuser
+ )
+ )
+ } else if (uninstallFromAllUsers && !isSingleUser) {
+ messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users))
+ } else if (uninstalledUser != myUserHandle) {
+ // Uninstalling user is issuing uninstall for another user
+ val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
+ .getSystemService(UserManager::class.java)
+ val userName = customUserManager!!.userName
+
+ val uninstalledUserType = getUninstalledUserType(myUserHandle, uninstalledUser!!)
+ val messageString: String
+ when (uninstalledUserType) {
+ UserManager.USER_TYPE_PROFILE_MANAGED -> {
+ messageString = context.getString(
+ R.string.uninstall_application_text_current_user_work_profile, userName
+ )
+ }
+
+ UserManager.USER_TYPE_PROFILE_CLONE -> {
+ isClonedApp = true
+ messageString = context.getString(
+ R.string.uninstall_application_text_current_user_clone_profile
+ )
+ }
+
+ else -> {
+ messageString = context.getString(
+ R.string.uninstall_application_text_user, userName
+ )
+ }
+
+ }
+ messageBuilder.append(messageString)
+ } else if (isCloneProfile(uninstalledUser!!)) {
+ isClonedApp = true
+ messageBuilder.append(context.getString(
+ R.string.uninstall_application_text_current_user_clone_profile
+ )
+ )
+ } else if (myUserHandle == UserHandle.SYSTEM
+ && hasClonedInstance(targetAppInfo!!.packageName)
+ ) {
+ messageBuilder.append(context.getString(
+ R.string.uninstall_application_text_with_clone_instance, targetAppLabel
+ )
+ )
+ } else {
+ messageBuilder.append(context.getString(R.string.uninstall_application_text))
+ }
+
+ val message = messageBuilder.toString()
+
+ val title = if (isClonedApp) {
+ context.getString(R.string.cloned_app_label, targetAppLabel)
+ } else {
+ targetAppLabel.toString()
+ }
+
+ var suggestToKeepAppData = false
+ try {
+ val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0)
+ suggestToKeepAppData =
+ pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData()
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e)
+ }
+
+ var appDataSize: Long = 0
+ if (suggestToKeepAppData) {
+ appDataSize = getAppDataSize(
+ targetPackageName!!,
+ if (uninstallFromAllUsers) null else uninstalledUser
+ )
+ }
+
+ return UninstallUserActionRequired(title, message, appDataSize)
+ }
+
+ /**
+ * Returns whether there is only one "full" user on this device.
+ *
+ * **Note:** On devices that use [headless system user mode]
+ * [android.os.UserManager.isHeadlessSystemUserMode], the system user is not "full",
+ * so it's not be considered in the calculation.
+ */
+ private fun isSingleUser(): Boolean {
+ val userCount = userManager!!.userCount
+ return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2)
+ }
+
+ /**
+ * Returns the type of the user from where an app is being uninstalled. We are concerned with
+ * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
+ * belong to the same profile group.
+ */
+ private fun getUninstalledUserType(
+ myUserHandle: UserHandle,
+ uninstalledUserHandle: UserHandle
+ ): String? {
+ if (!userManager!!.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
+ return null
+ }
+ val customUserManager = context.createContextAsUser(uninstalledUserHandle, 0)
+ .getSystemService(UserManager::class.java)
+ val userTypes =
+ arrayOf(UserManager.USER_TYPE_PROFILE_MANAGED, UserManager.USER_TYPE_PROFILE_CLONE)
+
+ for (userType in userTypes) {
+ if (customUserManager!!.isUserOfType(userType)) {
+ return userType
+ }
+ }
+ return null
+ }
+
+ private fun hasClonedInstance(packageName: String): Boolean {
+ // Check if clone user is present on the device.
+ var cloneUser: UserHandle? = null
+ val profiles = userManager!!.userProfiles
+
+ for (userHandle in profiles) {
+ if (userHandle != UserHandle.SYSTEM && isCloneProfile(userHandle)) {
+ cloneUser = userHandle
+ break
+ }
+ }
+ // Check if another instance of given package exists in clone user profile.
+ return try {
+ cloneUser != null
+ && packageManager.getPackageUidAsUser(
+ packageName, PackageManager.PackageInfoFlags.of(0), cloneUser.identifier
+ ) > 0
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ private fun isCloneProfile(userHandle: UserHandle): Boolean {
+ val customUserManager = context.createContextAsUser(userHandle, 0)
+ .getSystemService(UserManager::class.java)
+ return customUserManager!!.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)
+ }
+
+ /**
+ * Get number of bytes of the app data of the package.
+ *
+ * @param pkg The package that might have app data.
+ * @param user The user the package belongs to or `null` if files of all users should
+ * be counted.
+ * @return The number of bytes.
+ */
+ private fun getAppDataSize(pkg: String, user: UserHandle?): Long {
+ if (user != null) {
+ return getAppDataSizeForUser(pkg, user)
+ }
+ // We are uninstalling from all users. Get cumulative app data size for all users.
+ val userHandles = userManager!!.getUserHandles(true)
+ var totalAppDataSize: Long = 0
+ val numUsers = userHandles.size
+ for (i in 0 until numUsers) {
+ totalAppDataSize += getAppDataSizeForUser(pkg, userHandles[i])
+ }
+ return totalAppDataSize
+ }
+
+ /**
+ * Get number of bytes of the app data of the package.
+ *
+ * @param pkg The package that might have app data.
+ * @param user The user the package belongs to
+ * @return The number of bytes.
+ */
+ private fun getAppDataSizeForUser(pkg: String, user: UserHandle): Long {
+ val storageStatsManager = context.getSystemService(StorageStatsManager::class.java)
+ try {
+ val stats = storageStatsManager!!.queryStatsForPackage(
+ packageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user
+ )
+ return stats.getDataBytes()
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Cannot determine amount of app data for $pkg", e)
+ }
+ return 0
+ }
+
+ fun initiateUninstall(keepData: Boolean) {
+ // Get an uninstallId to track results and show a notification on non-TV devices.
+ uninstallId = try {
+ UninstallEventReceiver.addObserver(
+ context, EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult
+ )
+ } catch (e: OutOfIdsException) {
+ Log.e(LOG_TAG, "Failed to start uninstall", e)
+ handleUninstallResult(
+ PackageInstaller.STATUS_FAILURE,
+ PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+ )
+ return
+ }
+
+ // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
+ uninstallResult.value = UninstallUninstalling(targetAppLabel, isClonedApp)
+
+ val uninstallData = Bundle()
+ uninstallData.putInt(EXTRA_UNINSTALL_ID, uninstallId)
+ uninstallData.putString(EXTRA_PACKAGE_NAME, targetPackageName)
+ uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, uninstallFromAllUsers)
+ uninstallData.putCharSequence(EXTRA_APP_LABEL, targetAppLabel)
+ uninstallData.putBoolean(EXTRA_IS_CLONE_APP, isClonedApp)
+ Log.i(LOG_TAG, "Uninstalling extras = $uninstallData")
+
+ // Get a PendingIntent for result broadcast and issue an uninstall request
+ val broadcastIntent = Intent(BROADCAST_ACTION)
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId)
+ broadcastIntent.setPackage(context.packageName)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, uninstallId, broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ if (!startUninstall(
+ targetPackageName!!, uninstalledUser!!, pendingIntent, uninstallFromAllUsers,
+ keepData
+ )
+ ) {
+ handleUninstallResult(
+ PackageInstaller.STATUS_FAILURE,
+ PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+ )
+ }
+ }
+
+ private fun handleUninstallResult(
+ status: Int,
+ legacyStatus: Int,
+ message: String?,
+ serviceId: Int
+ ) {
+ if (callback != null) {
+ // The caller will be informed about the result via a callback
+ callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message)
+
+ // Since the caller already received the results, just finish the app at this point
+ uninstallResult.value = null
+ return
+ }
+ val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+ if (returnResult || callingActivity != null) {
+ val intent = Intent()
+ intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ uninstallResult.setValue(
+ UninstallSuccess(resultIntent = intent, activityResultCode = Activity.RESULT_OK)
+ )
+ } else {
+ uninstallResult.setValue(
+ UninstallFailed(
+ returnResult = true,
+ resultIntent = intent,
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ )
+ }
+ return
+ }
+
+ // Caller did not want the result back. So, we either show a Toast, or a Notification.
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ val statusMessage = if (isClonedApp) context.getString(
+ R.string.uninstall_done_clone_app, targetAppLabel
+ ) else context.getString(R.string.uninstall_done_app, targetAppLabel)
+ uninstallResult.setValue(
+ UninstallSuccess(activityResultCode = legacyStatus, message = statusMessage)
+ )
+ } else {
+ val uninstallFailureChannel = NotificationChannel(
+ UNINSTALL_FAILURE_CHANNEL,
+ context.getString(R.string.uninstall_failure_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+ notificationManager!!.createNotificationChannel(uninstallFailureChannel)
+
+ val uninstallFailedNotification: Notification.Builder =
+ Notification.Builder(context, UNINSTALL_FAILURE_CHANNEL)
+
+ val myUserHandle = Process.myUserHandle()
+ when (legacyStatus) {
+ PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
+ // Find out if the package is an active admin for some non-current user.
+ val otherBlockingUserHandle =
+ findUserOfDeviceAdmin(myUserHandle, targetPackageName!!)
+ if (otherBlockingUserHandle == null) {
+ Log.d(
+ LOG_TAG, "Uninstall failed because $targetPackageName"
+ + " is a device admin"
+ )
+ addDeviceManagerButton(context, uninstallFailedNotification)
+ setBigText(
+ uninstallFailedNotification, context.getString(
+ R.string.uninstall_failed_device_policy_manager
+ )
+ )
+ } else {
+ Log.d(
+ LOG_TAG, "Uninstall failed because $targetPackageName"
+ + " is a device admin of user $otherBlockingUserHandle"
+ )
+ val userName = context.createContextAsUser(otherBlockingUserHandle, 0)
+ .getSystemService(UserManager::class.java)!!.userName
+ setBigText(
+ uninstallFailedNotification, String.format(
+ context.getString(
+ R.string.uninstall_failed_device_policy_manager_of_user
+ ), userName
+ )
+ )
+ }
+ }
+
+ PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
+ val otherBlockingUserHandle = findBlockingUser(targetPackageName!!)
+ val isProfileOfOrSame = isProfileOfOrSame(
+ userManager!!, myUserHandle, otherBlockingUserHandle
+ )
+ if (isProfileOfOrSame) {
+ addDeviceManagerButton(context, uninstallFailedNotification)
+ } else {
+ addManageUsersButton(context, uninstallFailedNotification)
+ }
+ var bigText: String? = null
+ if (otherBlockingUserHandle == null) {
+ Log.d(
+ LOG_TAG, "Uninstall failed for $targetPackageName " +
+ "with code $status no blocking user"
+ )
+ } else if (otherBlockingUserHandle === UserHandle.SYSTEM) {
+ bigText = context.getString(R.string.uninstall_blocked_device_owner)
+ } else {
+ bigText = context.getString(
+ if (uninstallFromAllUsers) R.string.uninstall_all_blocked_profile_owner
+ else R.string.uninstall_blocked_profile_owner
+ )
+ }
+ bigText?.let { setBigText(uninstallFailedNotification, it) }
+ }
+
+ else -> {
+ Log.d(
+ LOG_TAG, "Uninstall blocked for $targetPackageName"
+ + " with legacy code $legacyStatus"
+ )
+ }
+ }
+ uninstallFailedNotification.setContentTitle(
+ context.getString(R.string.uninstall_failed_app, targetAppLabel)
+ )
+ uninstallFailedNotification.setOngoing(false)
+ uninstallFailedNotification.setSmallIcon(R.drawable.ic_error)
+
+ uninstallResult.setValue(
+ UninstallFailed(
+ returnResult = false,
+ uninstallNotificationId = uninstallId,
+ uninstallNotification = uninstallFailedNotification.build()
+ )
+ )
+ }
+ }
+
+ /**
+ * @param myUserHandle [UserHandle] of the current user.
+ * @param packageName Name of the package being uninstalled.
+ * @return the [UserHandle] of the user in which a package is a device admin.
+ */
+ private fun findUserOfDeviceAdmin(myUserHandle: UserHandle, packageName: String): UserHandle? {
+ for (otherUserHandle in userManager!!.getUserHandles(true)) {
+ // We only catch the case when the user in question is neither the
+ // current user nor its profile.
+ if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+ continue
+ }
+ val dpm = context.createContextAsUser(otherUserHandle, 0)
+ .getSystemService(DevicePolicyManager::class.java)
+ if (dpm!!.packageHasActiveAdmins(packageName)) {
+ return otherUserHandle
+ }
+ }
+ return null
+ }
+
+ /**
+ *
+ * @param packageName Name of the package being uninstalled.
+ * @return [UserHandle] of the user in which a package is blocked from being uninstalled.
+ */
+ private fun findBlockingUser(packageName: String): UserHandle? {
+ for (otherUserHandle in userManager!!.getUserHandles(true)) {
+ // TODO (b/307399586): Add a negation when the logic of the method is fixed
+ if (packageManager.canUserUninstall(packageName, otherUserHandle)) {
+ return otherUserHandle
+ }
+ }
+ return null
+ }
+
+ /**
+ * Set big text for the notification.
+ *
+ * @param builder The builder of the notification
+ * @param text The text to set.
+ */
+ private fun setBigText(
+ builder: Notification.Builder,
+ text: CharSequence
+ ) {
+ builder.setStyle(Notification.BigTextStyle().bigText(text))
+ }
+
+ /**
+ * Add a button to the notification that links to the user management.
+ *
+ * @param context The context the notification is created in
+ * @param builder The builder of the notification
+ */
+ private fun addManageUsersButton(
+ context: Context,
+ builder: Notification.Builder
+ ) {
+ builder.addAction(
+ Notification.Action.Builder(
+ Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
+ context.getString(R.string.manage_users),
+ PendingIntent.getActivity(
+ context, 0, getUserSettingsIntent(),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+ )
+ }
+
+ private fun getUserSettingsIntent(): Intent {
+ val intent = Intent(Settings.ACTION_USER_SETTINGS)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ /**
+ * Add a button to the notification that links to the device policy management.
+ *
+ * @param context The context the notification is created in
+ * @param builder The builder of the notification
+ */
+ private fun addDeviceManagerButton(
+ context: Context,
+ builder: Notification.Builder
+ ) {
+ builder.addAction(
+ Notification.Action.Builder(
+ Icon.createWithResource(context, R.drawable.ic_lock),
+ context.getString(R.string.manage_device_administrators),
+ PendingIntent.getActivity(
+ context, 0, getDeviceManagerIntent(),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+ )
+ }
+
+ private fun getDeviceManagerIntent(): Intent {
+ val intent = Intent()
+ intent.setClassName(
+ "com.android.settings",
+ "com.android.settings.Settings\$DeviceAdminSettingsActivity"
+ )
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ /**
+ * Starts an uninstall for the given package.
+ *
+ * @return `true` if there was no exception while uninstalling. This does not represent
+ * the result of the uninstall. Result will be made available in [handleUninstallResult]
+ */
+ private fun startUninstall(
+ packageName: String,
+ targetUser: UserHandle,
+ pendingIntent: PendingIntent,
+ uninstallFromAllUsers: Boolean,
+ keepData: Boolean
+ ): Boolean {
+ var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0
+ flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0
+
+ return try {
+ context.createContextAsUser(targetUser, 0)
+ .packageManager.packageInstaller.uninstall(
+ VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ flags, pendingIntent.intentSender
+ )
+ true
+ } catch (e: IllegalArgumentException) {
+ Log.e(LOG_TAG, "Failed to uninstall", e)
+ false
+ }
+ }
+
+ fun cancelInstall() {
+ if (callback != null) {
+ callback!!.onUninstallComplete(
+ targetPackageName!!,
+ PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"
+ )
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = UninstallRepository::class.java.simpleName
+ private const val UNINSTALL_FAILURE_CHANNEL = "uninstall_failure"
+ private const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"
+ private const val EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"
+ private const val EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"
+ private const val EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP"
+ private const val EXTRA_PACKAGE_NAME =
+ "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME"
+ }
+
+ class CallerInfo(val activityName: String?, val uid: Int)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
new file mode 100644
index 0000000..f086209
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
@@ -0,0 +1,112 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.app.Notification
+import android.content.Intent
+import com.android.packageinstaller.R
+
+sealed class UninstallStage(val stageCode: Int) {
+
+ companion object {
+ const val STAGE_DEFAULT = -1
+ const val STAGE_ABORTED = 0
+ const val STAGE_READY = 1
+ const val STAGE_USER_ACTION_REQUIRED = 2
+ const val STAGE_UNINSTALLING = 3
+ const val STAGE_SUCCESS = 4
+ const val STAGE_FAILED = 5
+ }
+}
+
+class UninstallReady : UninstallStage(STAGE_READY)
+
+data class UninstallUserActionRequired(
+ val title: String? = null,
+ val message: String? = null,
+ val appDataSize: Long = 0
+) : UninstallStage(STAGE_USER_ACTION_REQUIRED)
+
+data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) :
+ UninstallStage(STAGE_UNINSTALLING)
+
+data class UninstallSuccess(
+ val resultIntent: Intent? = null,
+ val activityResultCode: Int = 0,
+ val message: String? = null,
+) : UninstallStage(STAGE_SUCCESS)
+
+data class UninstallFailed(
+ val returnResult: Boolean,
+ /**
+ * If the caller wants the result back, the intent will hold the uninstall failure status code
+ * and legacy code.
+ */
+ val resultIntent: Intent? = null,
+ val activityResultCode: Int = Activity.RESULT_CANCELED,
+ /**
+ * ID used to show [uninstallNotification]
+ */
+ val uninstallNotificationId: Int? = null,
+ /**
+ * When the user does not request a result back, this notification will be shown indicating the
+ * reason for uninstall failure.
+ */
+ val uninstallNotification: Notification? = null,
+) : UninstallStage(STAGE_FAILED) {
+
+ init {
+ if (uninstallNotification != null && uninstallNotificationId == null) {
+ throw IllegalArgumentException(
+ "uninstallNotification cannot be set without uninstallNotificationId"
+ )
+ }
+ }
+}
+
+data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED) {
+
+ var dialogTitleResource = 0
+ var dialogTextResource = 0
+ val activityResultCode = Activity.RESULT_FIRST_USER
+
+ init {
+ when (abortReason) {
+ ABORT_REASON_APP_UNAVAILABLE -> {
+ dialogTitleResource = R.string.app_not_found_dlg_title
+ dialogTextResource = R.string.app_not_found_dlg_text
+ }
+
+ ABORT_REASON_USER_NOT_ALLOWED -> {
+ dialogTitleResource = 0
+ dialogTextResource = R.string.user_is_not_allowed_dlg_text
+ }
+
+ else -> {
+ dialogTitleResource = 0
+ dialogTextResource = R.string.generic_error_dlg_text
+ }
+ }
+ }
+
+ companion object {
+ const val ABORT_REASON_GENERIC_ERROR = 0
+ const val ABORT_REASON_APP_UNAVAILABLE = 1
+ const val ABORT_REASON_USER_NOT_ALLOWED = 2
+ }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
deleted file mode 100644
index 520b6c5..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
+++ /dev/null
@@ -1,127 +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.packageinstaller.v2.model.installstagedata;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class InstallAborted extends InstallStage {
-
- public static final int ABORT_REASON_INTERNAL_ERROR = 0;
- public static final int ABORT_REASON_POLICY = 1;
- public static final int ABORT_REASON_DONE = 2;
- public static final int DLG_PACKAGE_ERROR = 1;
- private final int mStage = InstallStage.STAGE_ABORTED;
- private final int mAbortReason;
-
- /**
- * It will hold the restriction name, when the restriction was enforced by the system, and not
- * a device admin.
- */
- @NonNull
- private final String mMessage;
- /**
- * <p>If abort reason is ABORT_REASON_POLICY, then this will hold the Intent
- * to display a support dialog when a feature was disabled by an admin. It will be
- * {@code null} if the feature is disabled by the system. In this case, the restriction name
- * will be set in {@link #mMessage} </p>
- *
- * <p>If the abort reason is ABORT_REASON_INTERNAL_ERROR, it <b>may</b> hold an
- * intent to be sent as a result to the calling activity.</p>
- */
- @Nullable
- private final Intent mIntent;
- private final int mErrorDialogType;
- private final int mActivityResultCode;
-
- private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent,
- int activityResultCode, int errorDialogType) {
- mAbortReason = reason;
- mMessage = message;
- mIntent = intent;
- mErrorDialogType = errorDialogType;
- mActivityResultCode = activityResultCode;
- }
-
- public int getAbortReason() {
- return mAbortReason;
- }
-
- @NonNull
- public String getMessage() {
- return mMessage;
- }
-
- @Nullable
- public Intent getResultIntent() {
- return mIntent;
- }
-
- public int getErrorDialogType() {
- return mErrorDialogType;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private final int mAbortReason;
- private String mMessage = "";
- private Intent mIntent = null;
- private int mActivityResultCode = Activity.RESULT_CANCELED;
- private int mErrorDialogType;
-
- public Builder(int reason) {
- mAbortReason = reason;
- }
-
- public Builder setMessage(@NonNull String message) {
- mMessage = message;
- return this;
- }
-
- public Builder setResultIntent(@NonNull Intent intent) {
- mIntent = intent;
- return this;
- }
-
- public Builder setErrorDialogType(int dialogType) {
- mErrorDialogType = dialogType;
- return this;
- }
-
- public Builder setActivityResultCode(int resultCode) {
- mActivityResultCode = resultCode;
- return this;
- }
-
- public InstallAborted build() {
- return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode,
- mErrorDialogType);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
deleted file mode 100644
index 67e1690..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
+++ /dev/null
@@ -1,69 +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.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallFailed extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_FAILED;
- @NonNull
- private final AppSnippet mAppSnippet;
- private final int mStatusCode;
- private final int mLegacyCode;
- @Nullable
- private final String mMessage;
-
- public InstallFailed(@NonNull AppSnippet appSnippet, int statusCode, int legacyCode,
- @Nullable String message) {
- mAppSnippet = appSnippet;
- mLegacyCode = statusCode;
- mStatusCode = legacyCode;
- mMessage = message;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @NonNull
- public Drawable getAppIcon() {
- return mAppSnippet.getIcon();
- }
-
- @NonNull
- public String getAppLabel() {
- return (String) mAppSnippet.getLabel();
- }
-
- public int getStatusCode() {
- return mStatusCode;
- }
-
- public int getLegacyCode() {
- return mLegacyCode;
- }
-
- @Nullable
- public String getMessage() {
- return mMessage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
deleted file mode 100644
index efd4947..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
+++ /dev/null
@@ -1,47 +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.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallInstalling extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_INSTALLING;
- @NonNull
- private final AppSnippet mAppSnippet;
-
- public InstallInstalling(@NonNull AppSnippet appSnippet) {
- mAppSnippet = appSnippet;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @NonNull
- public Drawable getAppIcon() {
- return mAppSnippet.getIcon();
- }
-
- @NonNull
- public String getAppLabel() {
- return (String) mAppSnippet.getLabel();
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
deleted file mode 100644
index 548f2c5..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
+++ /dev/null
@@ -1,27 +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
- *
- * https://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.packageinstaller.v2.model.installstagedata;
-
-public class InstallReady extends InstallStage{
-
- private final int mStage = InstallStage.STAGE_READY;
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
deleted file mode 100644
index f91e64b..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
+++ /dev/null
@@ -1,34 +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.packageinstaller.v2.model.installstagedata;
-
-public abstract class InstallStage {
-
- public static final int STAGE_DEFAULT = -1;
- public static final int STAGE_ABORTED = 0;
- public static final int STAGE_STAGING = 1;
- public static final int STAGE_READY = 2;
- public static final int STAGE_USER_ACTION_REQUIRED = 3;
- public static final int STAGE_INSTALLING = 4;
- public static final int STAGE_SUCCESS = 5;
- public static final int STAGE_FAILED = 6;
-
- /**
- * @return the integer value representing current install stage.
- */
- public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
deleted file mode 100644
index da48256..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
+++ /dev/null
@@ -1,95 +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.packageinstaller.v2.model.installstagedata;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallSuccess extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_SUCCESS;
-
- @NonNull
- private final AppSnippet mAppSnippet;
- private final boolean mShouldReturnResult;
- /**
- * <p>If the caller is requesting a result back, this will hold the Intent with
- * EXTRA_INSTALL_RESULT set to INSTALL_SUCCEEDED which is sent back to the caller.</p>
- * <p>If the caller doesn't want the result back, this will hold the Intent that launches
- * the newly installed / updated app.</p>
- */
- @NonNull
- private final Intent mResultIntent;
-
- public InstallSuccess(@NonNull AppSnippet appSnippet, boolean shouldReturnResult,
- @NonNull Intent launcherIntent) {
- mAppSnippet = appSnippet;
- mShouldReturnResult = shouldReturnResult;
- mResultIntent = launcherIntent;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @NonNull
- public Drawable getAppIcon() {
- return mAppSnippet.getIcon();
- }
-
- @NonNull
- public String getAppLabel() {
- return (String) mAppSnippet.getLabel();
- }
-
- public boolean shouldReturnResult() {
- return mShouldReturnResult;
- }
-
- @NonNull
- public Intent getResultIntent() {
- return mResultIntent;
- }
-
- public static class Builder {
-
- private final AppSnippet mAppSnippet;
- private boolean mShouldReturnResult;
- private Intent mLauncherIntent;
-
- public Builder(@NonNull AppSnippet appSnippet) {
- mAppSnippet = appSnippet;
- }
-
- public Builder setShouldReturnResult(boolean returnResult) {
- mShouldReturnResult = returnResult;
- return this;
- }
-
- public Builder setResultIntent(@NonNull Intent intent) {
- mLauncherIntent = intent;
- return this;
- }
-
- public InstallSuccess build() {
- return new InstallSuccess(mAppSnippet, mShouldReturnResult, mLauncherIntent);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
deleted file mode 100644
index 08a7487..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
+++ /dev/null
@@ -1,99 +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.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallUserActionRequired extends InstallStage {
-
- public static final int USER_ACTION_REASON_UNKNOWN_SOURCE = 0;
- public static final int USER_ACTION_REASON_ANONYMOUS_SOURCE = 1;
- public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2;
- private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED;
- private final int mActionReason;
- @Nullable
- private final AppSnippet mAppSnippet;
- private final boolean mIsAppUpdating;
- @Nullable
- private final String mDialogMessage;
-
- public InstallUserActionRequired(int actionReason, @Nullable AppSnippet appSnippet,
- boolean isUpdating, @Nullable String dialogMessage) {
- mActionReason = actionReason;
- mAppSnippet = appSnippet;
- mIsAppUpdating = isUpdating;
- mDialogMessage = dialogMessage;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @Nullable
- public Drawable getAppIcon() {
- return mAppSnippet != null ? mAppSnippet.getIcon() : null;
- }
-
- @Nullable
- public String getAppLabel() {
- return mAppSnippet != null ? (String) mAppSnippet.getLabel() : null;
- }
-
- public boolean isAppUpdating() {
- return mIsAppUpdating;
- }
-
- @Nullable
- public String getDialogMessage() {
- return mDialogMessage;
- }
-
- public int getActionReason() {
- return mActionReason;
- }
-
- public static class Builder {
-
- private final int mActionReason;
- private final AppSnippet mAppSnippet;
- private boolean mIsAppUpdating;
- private String mDialogMessage;
-
- public Builder(int actionReason, @Nullable AppSnippet appSnippet) {
- mActionReason = actionReason;
- mAppSnippet = appSnippet;
- }
-
- public Builder setAppUpdating(boolean isUpdating) {
- mIsAppUpdating = isUpdating;
- return this;
- }
-
- public Builder setDialogMessage(@Nullable String message) {
- mDialogMessage = message;
- return this;
- }
-
- public InstallUserActionRequired build() {
- return new InstallUserActionRequired(mActionReason, mAppSnippet, mIsAppUpdating,
- mDialogMessage);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
deleted file mode 100644
index 9aea6b1..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
+++ /dev/null
@@ -1,71 +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
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import com.android.packageinstaller.R;
-
-public class UninstallAborted extends UninstallStage {
-
- public static final int ABORT_REASON_GENERIC_ERROR = 0;
- public static final int ABORT_REASON_APP_UNAVAILABLE = 1;
- public static final int ABORT_REASON_USER_NOT_ALLOWED = 2;
- private final int mStage = UninstallStage.STAGE_ABORTED;
- private final int mAbortReason;
- private final int mDialogTitleResource;
- private final int mDialogTextResource;
- private final int mActivityResultCode = Activity.RESULT_FIRST_USER;
-
- public UninstallAborted(int abortReason) {
- mAbortReason = abortReason;
- switch (abortReason) {
- case ABORT_REASON_APP_UNAVAILABLE -> {
- mDialogTitleResource = R.string.app_not_found_dlg_title;
- mDialogTextResource = R.string.app_not_found_dlg_text;
- }
- case ABORT_REASON_USER_NOT_ALLOWED -> {
- mDialogTitleResource = 0;
- mDialogTextResource = R.string.user_is_not_allowed_dlg_text;
- }
- default -> {
- mDialogTitleResource = 0;
- mDialogTextResource = R.string.generic_error_dlg_text;
- }
- }
- }
-
- public int getAbortReason() {
- return mAbortReason;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- public int getDialogTitleResource() {
- return mDialogTitleResource;
- }
-
- public int getDialogTextResource() {
- return mDialogTextResource;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
deleted file mode 100644
index 6ed8883..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
+++ /dev/null
@@ -1,119 +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
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.Intent;
-
-public class UninstallFailed extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_FAILED;
- private final boolean mReturnResult;
- /**
- * If the caller wants the result back, the intent will hold the uninstall failure status code
- * and legacy code.
- */
- private final Intent mResultIntent;
- /**
- * When the user does not request a result back, this notification will be shown indicating the
- * reason for uninstall failure.
- */
- private final Notification mUninstallNotification;
- /**
- * ID used to show {@link #mUninstallNotification}
- */
- private final int mUninstallId;
- private final int mActivityResultCode;
-
- public UninstallFailed(boolean returnResult, Intent resultIntent, int activityResultCode,
- int uninstallId, Notification uninstallNotification) {
- mReturnResult = returnResult;
- mResultIntent = resultIntent;
- mActivityResultCode = activityResultCode;
- mUninstallId = uninstallId;
- mUninstallNotification = uninstallNotification;
- }
-
- public boolean returnResult() {
- return mReturnResult;
- }
-
- public Intent getResultIntent() {
- return mResultIntent;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- public Notification getUninstallNotification() {
- return mUninstallNotification;
- }
-
- public int getUninstallId() {
- return mUninstallId;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private final boolean mReturnResult;
- private int mActivityResultCode = Activity.RESULT_CANCELED;
- /**
- * See {@link UninstallFailed#mResultIntent}
- */
- private Intent mResultIntent = null;
- /**
- * See {@link UninstallFailed#mUninstallNotification}
- */
- private Notification mUninstallNotification;
- /**
- * See {@link UninstallFailed#mUninstallId}
- */
- private int mUninstallId;
-
- public Builder(boolean returnResult) {
- mReturnResult = returnResult;
- }
-
- public Builder setUninstallNotification(int uninstallId, Notification notification) {
- mUninstallId = uninstallId;
- mUninstallNotification = notification;
- return this;
- }
-
- public Builder setResultIntent(Intent intent) {
- mResultIntent = intent;
- return this;
- }
-
- public Builder setActivityResultCode(int resultCode) {
- mActivityResultCode = resultCode;
- return this;
- }
-
- public UninstallFailed build() {
- return new UninstallFailed(mReturnResult, mResultIntent, mActivityResultCode,
- mUninstallId, mUninstallNotification);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
deleted file mode 100644
index 0108cb4..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
+++ /dev/null
@@ -1,27 +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
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallReady extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_READY;
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
deleted file mode 100644
index 87ca4ec..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
+++ /dev/null
@@ -1,30 +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.packageinstaller.v2.model.uninstallstagedata;
-
-public abstract class UninstallStage {
-
- public static final int STAGE_DEFAULT = -1;
- public static final int STAGE_ABORTED = 0;
- public static final int STAGE_READY = 1;
- public static final int STAGE_USER_ACTION_REQUIRED = 2;
- public static final int STAGE_UNINSTALLING = 3;
- public static final int STAGE_SUCCESS = 4;
- public static final int STAGE_FAILED = 5;
-
- public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
deleted file mode 100644
index 5df6b02..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
+++ /dev/null
@@ -1,79 +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
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.content.Intent;
-
-public class UninstallSuccess extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_SUCCESS;
- private final String mMessage;
- private final Intent mResultIntent;
- private final int mActivityResultCode;
-
- public UninstallSuccess(Intent resultIntent, int activityResultCode, String message) {
- mResultIntent = resultIntent;
- mActivityResultCode = activityResultCode;
- mMessage = message;
- }
-
- public String getMessage() {
- return mMessage;
- }
-
- public Intent getResultIntent() {
- return mResultIntent;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private Intent mResultIntent;
- private int mActivityResultCode;
- private String mMessage;
-
- public Builder() {
- }
-
- public Builder setResultIntent(Intent intent) {
- mResultIntent = intent;
- return this;
- }
-
- public Builder setActivityResultCode(int resultCode) {
- mActivityResultCode = resultCode;
- return this;
- }
-
- public Builder setMessage(String message) {
- mMessage = message;
- return this;
- }
-
- public UninstallSuccess build() {
- return new UninstallSuccess(mResultIntent, mActivityResultCode, mMessage);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
deleted file mode 100644
index f5156cb..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
+++ /dev/null
@@ -1,43 +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
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUninstalling extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_UNINSTALLING;
-
- private final CharSequence mAppLabel;
- private final boolean mIsCloneUser;
-
- public UninstallUninstalling(CharSequence appLabel, boolean isCloneUser) {
- mAppLabel = appLabel;
- mIsCloneUser = isCloneUser;
- }
-
- public CharSequence getAppLabel() {
- return mAppLabel;
- }
-
- public boolean isCloneUser() {
- return mIsCloneUser;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
deleted file mode 100644
index b600149..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
+++ /dev/null
@@ -1,74 +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
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUserActionRequired extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_USER_ACTION_REQUIRED;
- private final String mTitle;
- private final String mMessage;
- private final long mAppDataSize;
-
- public UninstallUserActionRequired(String title, String message, long appDataSize) {
- mTitle = title;
- mMessage = message;
- mAppDataSize = appDataSize;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getMessage() {
- return mMessage;
- }
-
- public long getAppDataSize() {
- return mAppDataSize;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private String mTitle;
- private String mMessage;
- private long mAppDataSize = 0;
-
- public Builder setTitle(String title) {
- mTitle = title;
- return this;
- }
-
- public Builder setMessage(String message) {
- mMessage = message;
- return this;
- }
-
- public Builder setAppDataSize(long appDataSize) {
- mAppDataSize = appDataSize;
- return this;
- }
-
- public UninstallUserActionRequired build() {
- return new UninstallUserActionRequired(mTitle, mMessage, mAppDataSize);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
deleted file mode 100644
index fdb024f..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
+++ /dev/null
@@ -1,38 +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.packageinstaller.v2.ui;
-
-import android.content.Intent;
-
-public interface InstallActionListener {
-
- /**
- * Method to handle a positive response from the user
- */
- void onPositiveResponse(int stageCode);
-
- /**
- * Method to dispatch intent for toggling "install from unknown sources" setting for a package
- */
- void sendUnknownAppsIntent(String packageName);
-
- /**
- * Method to handle a negative response from the user
- */
- void onNegativeResponse(int stageCode);
- void openInstalledApp(Intent intent);
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
new file mode 100644
index 0000000..c109fc6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.packageinstaller.v2.ui
+
+import android.content.Intent
+
+interface InstallActionListener {
+ /**
+ * Method to handle a positive response from the user.
+ */
+ fun onPositiveResponse(reasonCode: Int)
+
+ /**
+ * Method to dispatch intent for toggling "install from unknown sources" setting for a package.
+ */
+ fun sendUnknownAppsIntent(sourcePackageName: String)
+
+ /**
+ * Method to handle a negative response from the user.
+ */
+ fun onNegativeResponse(stageCode: Int)
+
+ /**
+ * Launch the intent to open the newly installed / updated app.
+ */
+ fun openInstalledApp(intent: Intent?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
deleted file mode 100644
index d06b4b3..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
+++ /dev/null
@@ -1,354 +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.packageinstaller.v2.ui;
-
-import static android.content.Intent.CATEGORY_LAUNCHER;
-import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.os.Process.INVALID_UID;
-import static com.android.packageinstaller.v2.model.InstallRepository.EXTRA_STAGED_SESSION_ID;
-
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Window;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment;
-import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment;
-import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory;
-import java.util.ArrayList;
-import java.util.List;
-
-public class InstallLaunch extends FragmentActivity implements InstallActionListener {
-
- public static final String EXTRA_CALLING_PKG_UID =
- InstallLaunch.class.getPackageName() + ".callingPkgUid";
- public static final String EXTRA_CALLING_PKG_NAME =
- InstallLaunch.class.getPackageName() + ".callingPkgName";
- private static final String TAG = InstallLaunch.class.getSimpleName();
- private static final String TAG_DIALOG = "dialog";
- private final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
- private final boolean mLocalLOGV = false;
- /**
- * A collection of unknown sources listeners that are actively listening for app ops mode
- * changes
- */
- private final List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1);
- private InstallViewModel mInstallViewModel;
- private InstallRepository mInstallRepository;
- private FragmentManager mFragmentManager;
- private AppOpsManager mAppOpsManager;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
-
- mFragmentManager = getSupportFragmentManager();
- mAppOpsManager = getSystemService(AppOpsManager.class);
-
- mInstallRepository = new InstallRepository(getApplicationContext());
- mInstallViewModel = new ViewModelProvider(this,
- new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get(
- InstallViewModel.class);
-
- Intent intent = getIntent();
- CallerInfo info = new CallerInfo(
- intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
- intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
- mInstallViewModel.preprocessIntent(intent, info);
-
- mInstallViewModel.getCurrentInstallStage().observe(this, this::onInstallStageChange);
- }
-
- /**
- * Main controller of the UI. This method shows relevant dialogs based on the install stage
- */
- private void onInstallStageChange(InstallStage installStage) {
- switch (installStage.getStageCode()) {
- case InstallStage.STAGE_STAGING -> {
- InstallStagingFragment stagingDialog = new InstallStagingFragment();
- showDialogInner(stagingDialog);
- mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
- }
- case InstallStage.STAGE_ABORTED -> {
- InstallAborted aborted = (InstallAborted) installStage;
- switch (aborted.getAbortReason()) {
- // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR
- case InstallAborted.ABORT_REASON_DONE,
- InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
- setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true);
- case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted);
- default -> setResult(RESULT_CANCELED, null, true);
- }
- }
- case InstallStage.STAGE_USER_ACTION_REQUIRED -> {
- InstallUserActionRequired uar = (InstallUserActionRequired) installStage;
- switch (uar.getActionReason()) {
- case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
- InstallConfirmationFragment actionDialog =
- new InstallConfirmationFragment(uar);
- showDialogInner(actionDialog);
- }
- case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
- ExternalSourcesBlockedFragment externalSourceDialog =
- new ExternalSourcesBlockedFragment(uar);
- showDialogInner(externalSourceDialog);
- }
- case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
- AnonymousSourceFragment anonymousSourceDialog =
- new AnonymousSourceFragment();
- showDialogInner(anonymousSourceDialog);
- }
- }
- }
- case InstallStage.STAGE_INSTALLING -> {
- InstallInstalling installing = (InstallInstalling) installStage;
- InstallInstallingFragment installingDialog =
- new InstallInstallingFragment(installing);
- showDialogInner(installingDialog);
- }
- case InstallStage.STAGE_SUCCESS -> {
- InstallSuccess success = (InstallSuccess) installStage;
- if (success.shouldReturnResult()) {
- Intent successIntent = success.getResultIntent();
- setResult(Activity.RESULT_OK, successIntent, true);
- } else {
- InstallSuccessFragment successFragment = new InstallSuccessFragment(success);
- showDialogInner(successFragment);
- }
- }
- case InstallStage.STAGE_FAILED -> {
- InstallFailed failed = (InstallFailed) installStage;
- InstallFailedFragment failedDialog = new InstallFailedFragment(failed);
- showDialogInner(failedDialog);
- }
- default -> {
- Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
- showDialogInner(null);
- }
- }
- }
-
- private void showPolicyRestrictionDialog(InstallAborted aborted) {
- String restriction = aborted.getMessage();
- Intent adminSupportIntent = aborted.getResultIntent();
- boolean shouldFinish;
-
- // If the given restriction is set by an admin, display information about the
- // admin enforcing the restriction for the affected user. If not enforced by the admin,
- // show the system dialog.
- if (adminSupportIntent != null) {
- if (mLocalLOGV) {
- Log.i(TAG, "Restriction set by admin, starting " + adminSupportIntent);
- }
- startActivity(adminSupportIntent);
- // Finish the package installer app since the next dialog will not be shown by this app
- shouldFinish = true;
- } else {
- if (mLocalLOGV) {
- Log.i(TAG, "Restriction set by system: " + restriction);
- }
- DialogFragment blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction);
- // Don't finish the package installer app since the next dialog
- // will be shown by this app
- shouldFinish = false;
- showDialogInner(blockedByPolicyDialog);
- }
- setResult(RESULT_CANCELED, null, shouldFinish);
- }
-
- /**
- * Create a new dialog based on the install restriction enforced.
- *
- * @param restriction The restriction to create the dialog for
- * @return The dialog
- */
- private DialogFragment createDevicePolicyRestrictionDialog(String restriction) {
- if (mLocalLOGV) {
- Log.i(TAG, "createDialog(" + restriction + ")");
- }
- return switch (restriction) {
- case UserManager.DISALLOW_INSTALL_APPS ->
- new SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text);
- case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
- new SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text);
- default -> null;
- };
- }
-
- /**
- * Replace any visible dialog by the dialog returned by InstallRepository
- *
- * @param newDialog The new dialog to display
- */
- private void showDialogInner(@Nullable DialogFragment newDialog) {
- DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
- TAG_DIALOG);
- if (currentDialog != null) {
- currentDialog.dismissAllowingStateLoss();
- }
- if (newDialog != null) {
- newDialog.show(mFragmentManager, TAG_DIALOG);
- }
- }
-
- public void setResult(int resultCode, Intent data, boolean shouldFinish) {
- super.setResult(resultCode, data);
- if (shouldFinish) {
- finish();
- }
- }
-
- @Override
- public void onPositiveResponse(int reasonCode) {
- switch (reasonCode) {
- case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
- mInstallViewModel.forcedSkipSourceCheck();
- case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
- mInstallViewModel.initiateInstall();
- }
- }
-
- @Override
- public void onNegativeResponse(int stageCode) {
- if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
- mInstallViewModel.cleanupInstall();
- }
- setResult(Activity.RESULT_CANCELED, null, true);
- }
-
- @Override
- public void sendUnknownAppsIntent(String sourcePackageName) {
- Intent settingsIntent = new Intent();
- settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
- final Uri packageUri = Uri.parse("package:" + sourcePackageName);
- settingsIntent.setData(packageUri);
- settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY);
-
- try {
- registerAppOpChangeListener(new UnknownSourcesListener(sourcePackageName),
- sourcePackageName);
- startActivityForResult(settingsIntent, REQUEST_TRUST_EXTERNAL_SOURCE);
- } catch (ActivityNotFoundException exc) {
- Log.e(TAG, "Settings activity not found for action: "
- + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
- }
- }
-
- @Override
- public void openInstalledApp(Intent intent) {
- setResult(RESULT_OK, intent, true);
- if (intent != null && intent.hasCategory(CATEGORY_LAUNCHER)) {
- startActivity(intent);
- }
- }
-
- private void registerAppOpChangeListener(UnknownSourcesListener listener, String packageName) {
- mAppOpsManager.startWatchingMode(
- AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, packageName,
- listener);
- mActiveUnknownSourcesListeners.add(listener);
- }
-
- private void unregisterAppOpChangeListener(UnknownSourcesListener listener) {
- mActiveUnknownSourcesListeners.remove(listener);
- mAppOpsManager.stopWatchingMode(listener);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_TRUST_EXTERNAL_SOURCE) {
- mInstallViewModel.reattemptInstall();
- } else {
- setResult(Activity.RESULT_CANCELED, null, true);
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- while (!mActiveUnknownSourcesListeners.isEmpty()) {
- unregisterAppOpChangeListener(mActiveUnknownSourcesListeners.get(0));
- }
- }
-
- private class UnknownSourcesListener implements AppOpsManager.OnOpChangedListener {
-
- private final String mOriginatingPackage;
-
- public UnknownSourcesListener(String originatingPackage) {
- mOriginatingPackage = originatingPackage;
- }
-
- @Override
- public void onOpChanged(String op, String packageName) {
- if (!mOriginatingPackage.equals(packageName)) {
- return;
- }
- unregisterAppOpChangeListener(this);
- mActiveUnknownSourcesListeners.remove(this);
- if (isDestroyed()) {
- return;
- }
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
- if (!isDestroyed()) {
- // Relaunch Pia to continue installation.
- startActivity(getIntent()
- .putExtra(EXTRA_STAGED_SESSION_ID, mInstallViewModel.getStagedSessionId()));
-
- // If the userId of the root of activity stack is different from current userId,
- // starting Pia again lead to duplicate instances of the app in the stack.
- // As such, finish the old instance. Old Pia is finished even if the userId of
- // the root is the same, since there is no way to determine the difference in
- // userIds.
- finish();
- }
- }, 500);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
new file mode 100644
index 0000000..2b610d7
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -0,0 +1,348 @@
+/*
+ * 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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Process
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import android.view.Window
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.R
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallAborted
+import com.android.packageinstaller.v2.model.InstallFailed
+import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallSuccess
+import com.android.packageinstaller.v2.model.InstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment
+import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
+import com.android.packageinstaller.v2.viewmodel.InstallViewModel
+import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
+
+class InstallLaunch : FragmentActivity(), InstallActionListener {
+
+ companion object {
+ @JvmField val EXTRA_CALLING_PKG_UID =
+ InstallLaunch::class.java.packageName + ".callingPkgUid"
+ @JvmField val EXTRA_CALLING_PKG_NAME =
+ InstallLaunch::class.java.packageName + ".callingPkgName"
+ private val LOG_TAG = InstallLaunch::class.java.simpleName
+ private const val TAG_DIALOG = "dialog"
+ }
+
+ private val localLOGV = false
+
+ /**
+ * A collection of unknown sources listeners that are actively listening for app ops mode
+ * changes
+ */
+ private val activeUnknownSourcesListeners: MutableList<UnknownSourcesListener> = ArrayList(1)
+ private var installViewModel: InstallViewModel? = null
+ private var installRepository: InstallRepository? = null
+ private var fragmentManager: FragmentManager? = null
+ private var appOpsManager: AppOpsManager? = null
+ private lateinit var unknownAppsIntentLauncher: ActivityResultLauncher<Intent>
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ requestWindowFeature(Window.FEATURE_NO_TITLE)
+ fragmentManager = supportFragmentManager
+ appOpsManager = getSystemService(AppOpsManager::class.java)
+ installRepository = InstallRepository(applicationContext)
+ installViewModel = ViewModelProvider(
+ this, InstallViewModelFactory(this.application, installRepository!!)
+ )[InstallViewModel::class.java]
+
+ val intent = intent
+ val info = InstallRepository.CallerInfo(
+ intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
+ intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+ )
+ installViewModel!!.preprocessIntent(intent, info)
+ installViewModel!!.currentInstallStage.observe(this) { installStage: InstallStage ->
+ onInstallStageChange(installStage)
+ }
+
+ // Used to launch intent for Settings, to manage "install unknown apps" permission
+ unknownAppsIntentLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ // Reattempt installation on coming back from Settings, after toggling
+ // "install unknown apps" permission
+ installViewModel!!.reattemptInstall()
+ }
+ }
+
+ /**
+ * Main controller of the UI. This method shows relevant dialogs based on the install stage
+ */
+ private fun onInstallStageChange(installStage: InstallStage) {
+ when (installStage.stageCode) {
+ InstallStage.STAGE_STAGING -> {
+ val stagingDialog = InstallStagingFragment()
+ showDialogInner(stagingDialog)
+ installViewModel!!.stagingProgress.observe(this) { progress: Int ->
+ stagingDialog.setProgress(progress)
+ }
+ }
+
+ InstallStage.STAGE_ABORTED -> {
+ val aborted = installStage as InstallAborted
+ when (aborted.abortReason) {
+ InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
+ setResult(aborted.activityResultCode, aborted.resultIntent, true)
+
+ InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
+ else -> setResult(Activity.RESULT_CANCELED, null, true)
+ }
+ }
+
+ InstallStage.STAGE_USER_ACTION_REQUIRED -> {
+ val uar = installStage as InstallUserActionRequired
+ when (uar.actionReason) {
+ InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
+ val actionDialog = InstallConfirmationFragment(uar)
+ showDialogInner(actionDialog)
+ }
+
+ InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
+ val externalSourceDialog = ExternalSourcesBlockedFragment(uar)
+ showDialogInner(externalSourceDialog)
+ }
+
+ InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
+ val anonymousSourceDialog = AnonymousSourceFragment()
+ showDialogInner(anonymousSourceDialog)
+ }
+ }
+ }
+
+ InstallStage.STAGE_INSTALLING -> {
+ val installing = installStage as InstallInstalling
+ val installingDialog = InstallInstallingFragment(installing)
+ showDialogInner(installingDialog)
+ }
+
+ InstallStage.STAGE_SUCCESS -> {
+ val success = installStage as InstallSuccess
+ if (success.shouldReturnResult) {
+ val successIntent = success.resultIntent
+ setResult(Activity.RESULT_OK, successIntent, true)
+ } else {
+ val successFragment = InstallSuccessFragment(success)
+ showDialogInner(successFragment)
+ }
+ }
+
+ InstallStage.STAGE_FAILED -> {
+ val failed = installStage as InstallFailed
+ val failedDialog = InstallFailedFragment(failed)
+ showDialogInner(failedDialog)
+ }
+
+ else -> {
+ Log.d(LOG_TAG, "Unimplemented stage: " + installStage.stageCode)
+ showDialogInner(null)
+ }
+ }
+ }
+
+ private fun showPolicyRestrictionDialog(aborted: InstallAborted) {
+ val restriction = aborted.message
+ val adminSupportIntent = aborted.resultIntent
+ var shouldFinish: Boolean = false
+
+ // If the given restriction is set by an admin, display information about the
+ // admin enforcing the restriction for the affected user. If not enforced by the admin,
+ // show the system dialog.
+ if (adminSupportIntent != null) {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Restriction set by admin, starting $adminSupportIntent")
+ }
+ startActivity(adminSupportIntent)
+ // Finish the package installer app since the next dialog will not be shown by this app
+ shouldFinish = true
+ } else {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Restriction set by system: $restriction")
+ }
+ val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
+ // Don't finish the package installer app since the next dialog
+ // will be shown by this app
+ shouldFinish = blockedByPolicyDialog != null
+ showDialogInner(blockedByPolicyDialog)
+ }
+ setResult(Activity.RESULT_CANCELED, null, shouldFinish)
+ }
+
+ /**
+ * Create a new dialog based on the install restriction enforced.
+ *
+ * @param restriction The restriction to create the dialog for
+ * @return The dialog
+ */
+ private fun createDevicePolicyRestrictionDialog(restriction: String?): DialogFragment? {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "createDialog($restriction)")
+ }
+ return when (restriction) {
+ UserManager.DISALLOW_INSTALL_APPS ->
+ SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text)
+
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
+ SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text)
+
+ else -> null
+ }
+ }
+
+ /**
+ * Replace any visible dialog by the dialog returned by InstallRepository
+ *
+ * @param newDialog The new dialog to display
+ */
+ private fun showDialogInner(newDialog: DialogFragment?) {
+ val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+ currentDialog?.dismissAllowingStateLoss()
+ newDialog?.show(fragmentManager!!, TAG_DIALOG)
+ }
+
+ fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+ super.setResult(resultCode, data)
+ if (shouldFinish) {
+ finish()
+ }
+ }
+
+ override fun onPositiveResponse(reasonCode: Int) {
+ when (reasonCode) {
+ InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
+ installViewModel!!.forcedSkipSourceCheck()
+
+ InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
+ installViewModel!!.initiateInstall()
+ }
+ }
+
+ override fun onNegativeResponse(stageCode: Int) {
+ if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
+ installViewModel!!.cleanupInstall()
+ }
+ setResult(Activity.RESULT_CANCELED, null, true)
+ }
+
+ override fun sendUnknownAppsIntent(sourcePackageName: String) {
+ val settingsIntent = Intent()
+ settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
+ val packageUri = Uri.parse("package:$sourcePackageName")
+ settingsIntent.setData(packageUri)
+ settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+ try {
+ registerAppOpChangeListener(
+ UnknownSourcesListener(sourcePackageName), sourcePackageName
+ )
+ unknownAppsIntentLauncher.launch(settingsIntent)
+ } catch (exc: ActivityNotFoundException) {
+ Log.e(
+ LOG_TAG, "Settings activity not found for action: "
+ + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES
+ )
+ }
+ }
+
+ override fun openInstalledApp(intent: Intent?) {
+ setResult(Activity.RESULT_OK, intent, true)
+ if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
+ startActivity(intent)
+ }
+ }
+
+ private fun registerAppOpChangeListener(listener: UnknownSourcesListener, packageName: String) {
+ appOpsManager!!.startWatchingMode(
+ AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES,
+ packageName,
+ listener
+ )
+ activeUnknownSourcesListeners.add(listener)
+ }
+
+ private fun unregisterAppOpChangeListener(listener: UnknownSourcesListener) {
+ activeUnknownSourcesListeners.remove(listener)
+ appOpsManager!!.stopWatchingMode(listener)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ while (activeUnknownSourcesListeners.isNotEmpty()) {
+ unregisterAppOpChangeListener(activeUnknownSourcesListeners[0])
+ }
+ }
+
+ private inner class UnknownSourcesListener(private val mOriginatingPackage: String) :
+ AppOpsManager.OnOpChangedListener {
+ override fun onOpChanged(op: String, packageName: String) {
+ if (mOriginatingPackage != packageName) {
+ return
+ }
+ unregisterAppOpChangeListener(this)
+ activeUnknownSourcesListeners.remove(this)
+ if (isDestroyed) {
+ return
+ }
+ Handler(Looper.getMainLooper()).postDelayed({
+ if (!isDestroyed) {
+ // Relaunch Pia to continue installation.
+ startActivity(
+ intent.putExtra(
+ InstallRepository.EXTRA_STAGED_SESSION_ID,
+ installViewModel!!.stagedSessionId
+ )
+ )
+
+ // If the userId of the root of activity stack is different from current userId,
+ // starting Pia again lead to duplicate instances of the app in the stack.
+ // As such, finish the old instance. Old Pia is finished even if the userId of
+ // the root is the same, since there is no way to determine the difference in
+ // userIds.
+ finish()
+ }
+ }, 500)
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
similarity index 78%
rename from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
rename to packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
index b8a9355..33f5db3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.ui;
+package com.android.packageinstaller.v2.ui
-public interface UninstallActionListener {
-
- void onPositiveResponse(boolean keepData);
-
- void onNegativeResponse();
+interface UninstallActionListener {
+ fun onPositiveResponse(keepData: Boolean)
+ fun onNegativeResponse()
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
deleted file mode 100644
index 7638e91..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
+++ /dev/null
@@ -1,167 +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
- *
- * https://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.packageinstaller.v2.ui;
-
-import static android.os.Process.INVALID_UID;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory;
-
-public class UninstallLaunch extends FragmentActivity implements UninstallActionListener {
-
- public static final String EXTRA_CALLING_PKG_UID =
- UninstallLaunch.class.getPackageName() + ".callingPkgUid";
- public static final String EXTRA_CALLING_ACTIVITY_NAME =
- UninstallLaunch.class.getPackageName() + ".callingActivityName";
- public static final String TAG = UninstallLaunch.class.getSimpleName();
- private static final String TAG_DIALOG = "dialog";
-
- private UninstallViewModel mUninstallViewModel;
- private UninstallRepository mUninstallRepository;
- private FragmentManager mFragmentManager;
- private NotificationManager mNotificationManager;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
- // Never restore any state, esp. never create any fragments. The data in the fragment might
- // be stale, if e.g. the app was uninstalled while the activity was destroyed.
- super.onCreate(null);
-
- mFragmentManager = getSupportFragmentManager();
- mNotificationManager = getSystemService(NotificationManager.class);
-
- mUninstallRepository = new UninstallRepository(getApplicationContext());
- mUninstallViewModel = new ViewModelProvider(this,
- new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get(
- UninstallViewModel.class);
-
- Intent intent = getIntent();
- CallerInfo callerInfo = new CallerInfo(
- intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
- intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
- mUninstallViewModel.preprocessIntent(intent, callerInfo);
-
- mUninstallViewModel.getCurrentUninstallStage().observe(this,
- this::onUninstallStageChange);
- }
-
- /**
- * Main controller of the UI. This method shows relevant dialogs / fragments based on the
- * uninstall stage
- */
- private void onUninstallStageChange(UninstallStage uninstallStage) {
- if (uninstallStage.getStageCode() == UninstallStage.STAGE_ABORTED) {
- UninstallAborted aborted = (UninstallAborted) uninstallStage;
- if (aborted.getAbortReason() == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
- aborted.getAbortReason() == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED) {
- UninstallErrorFragment errorDialog = new UninstallErrorFragment(aborted);
- showDialogInner(errorDialog);
- } else {
- setResult(aborted.getActivityResultCode(), null, true);
- }
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_USER_ACTION_REQUIRED) {
- UninstallUserActionRequired uar = (UninstallUserActionRequired) uninstallStage;
- UninstallConfirmationFragment confirmationDialog = new UninstallConfirmationFragment(
- uar);
- showDialogInner(confirmationDialog);
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_UNINSTALLING) {
- // TODO: This shows a fragment whether or not user requests a result or not.
- // Originally, if the user does not request a result, we used to show a notification.
- // And a fragment if the user requests a result back. Should we consolidate and
- // show a fragment always?
- UninstallUninstalling uninstalling = (UninstallUninstalling) uninstallStage;
- UninstallUninstallingFragment uninstallingDialog = new UninstallUninstallingFragment(
- uninstalling);
- showDialogInner(uninstallingDialog);
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_FAILED) {
- UninstallFailed failed = (UninstallFailed) uninstallStage;
- if (!failed.returnResult()) {
- mNotificationManager.notify(failed.getUninstallId(),
- failed.getUninstallNotification());
- }
- setResult(failed.getActivityResultCode(), failed.getResultIntent(), true);
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_SUCCESS) {
- UninstallSuccess success = (UninstallSuccess) uninstallStage;
- if (success.getMessage() != null) {
- Toast.makeText(this, success.getMessage(), Toast.LENGTH_LONG).show();
- }
- setResult(success.getActivityResultCode(), success.getResultIntent(), true);
- } else {
- Log.e(TAG, "Invalid stage: " + uninstallStage.getStageCode());
- showDialogInner(null);
- }
- }
-
- /**
- * Replace any visible dialog by the dialog returned by InstallRepository
- *
- * @param newDialog The new dialog to display
- */
- private void showDialogInner(DialogFragment newDialog) {
- DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
- TAG_DIALOG);
- if (currentDialog != null) {
- currentDialog.dismissAllowingStateLoss();
- }
- if (newDialog != null) {
- newDialog.show(mFragmentManager, TAG_DIALOG);
- }
- }
-
- public void setResult(int resultCode, Intent data, boolean shouldFinish) {
- super.setResult(resultCode, data);
- if (shouldFinish) {
- finish();
- }
- }
-
- @Override
- public void onPositiveResponse(boolean keepData) {
- mUninstallViewModel.initiateUninstall(keepData);
- }
-
- @Override
- public void onNegativeResponse() {
- mUninstallViewModel.cancelInstall();
- setResult(Activity.RESULT_FIRST_USER, null, true);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
new file mode 100644
index 0000000..0050c7e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.content.Intent
+import android.os.Bundle
+import android.os.Process
+import android.util.Log
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallAborted
+import com.android.packageinstaller.v2.model.UninstallFailed
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+import com.android.packageinstaller.v2.model.UninstallSuccess
+import com.android.packageinstaller.v2.model.UninstallUninstalling
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModel
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory
+
+class UninstallLaunch : FragmentActivity(), UninstallActionListener {
+
+ companion object {
+ @JvmField val EXTRA_CALLING_PKG_UID =
+ UninstallLaunch::class.java.packageName + ".callingPkgUid"
+ @JvmField val EXTRA_CALLING_ACTIVITY_NAME =
+ UninstallLaunch::class.java.packageName + ".callingActivityName"
+ val LOG_TAG = UninstallLaunch::class.java.simpleName
+ private const val TAG_DIALOG = "dialog"
+ }
+
+ private var uninstallViewModel: UninstallViewModel? = null
+ private var uninstallRepository: UninstallRepository? = null
+ private var fragmentManager: FragmentManager? = null
+ private var notificationManager: NotificationManager? = null
+ override fun onCreate(savedInstanceState: Bundle?) {
+ window.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+ // Never restore any state, esp. never create any fragments. The data in the fragment might
+ // be stale, if e.g. the app was uninstalled while the activity was destroyed.
+ super.onCreate(null)
+ fragmentManager = supportFragmentManager
+ notificationManager = getSystemService(NotificationManager::class.java)
+
+ uninstallRepository = UninstallRepository(applicationContext)
+ uninstallViewModel = ViewModelProvider(
+ this, UninstallViewModelFactory(this.application, uninstallRepository!!)
+ ).get(UninstallViewModel::class.java)
+
+ val intent = intent
+ val callerInfo = UninstallRepository.CallerInfo(
+ intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
+ intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+ )
+ uninstallViewModel!!.preprocessIntent(intent, callerInfo)
+ uninstallViewModel!!.currentUninstallStage.observe(this) { uninstallStage: UninstallStage ->
+ onUninstallStageChange(uninstallStage)
+ }
+ }
+
+ /**
+ * Main controller of the UI. This method shows relevant dialogs / fragments based on the
+ * uninstall stage
+ */
+ private fun onUninstallStageChange(uninstallStage: UninstallStage) {
+ when (uninstallStage.stageCode) {
+ UninstallStage.STAGE_ABORTED -> {
+ val aborted = uninstallStage as UninstallAborted
+ if (aborted.abortReason == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
+ aborted.abortReason == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED
+ ) {
+ val errorDialog = UninstallErrorFragment(aborted)
+ showDialogInner(errorDialog)
+ } else {
+ setResult(aborted.activityResultCode, null, true)
+ }
+ }
+
+ UninstallStage.STAGE_USER_ACTION_REQUIRED -> {
+ val uar = uninstallStage as UninstallUserActionRequired
+ val confirmationDialog = UninstallConfirmationFragment(uar)
+ showDialogInner(confirmationDialog)
+ }
+
+ UninstallStage.STAGE_UNINSTALLING -> {
+ // TODO: This shows a fragment whether or not user requests a result or not.
+ // Originally, if the user does not request a result, we used to show a notification.
+ // And a fragment if the user requests a result back. Should we consolidate and
+ // show a fragment always?
+ val uninstalling = uninstallStage as UninstallUninstalling
+ val uninstallingDialog = UninstallUninstallingFragment(uninstalling)
+ showDialogInner(uninstallingDialog)
+ }
+
+ UninstallStage.STAGE_FAILED -> {
+ val failed = uninstallStage as UninstallFailed
+ if (!failed.returnResult) {
+ notificationManager!!.notify(
+ failed.uninstallNotificationId!!, failed.uninstallNotification
+ )
+ }
+ setResult(failed.activityResultCode, failed.resultIntent, true)
+ }
+
+ UninstallStage.STAGE_SUCCESS -> {
+ val success = uninstallStage as UninstallSuccess
+ if (success.message != null) {
+ Toast.makeText(this, success.message, Toast.LENGTH_LONG).show()
+ }
+ setResult(success.activityResultCode, success.resultIntent, true)
+ }
+
+ else -> {
+ Log.e(LOG_TAG, "Invalid stage: " + uninstallStage.stageCode)
+ showDialogInner(null)
+ }
+ }
+ }
+
+ /**
+ * Replace any visible dialog by the dialog returned by InstallRepository
+ *
+ * @param newDialog The new dialog to display
+ */
+ private fun showDialogInner(newDialog: DialogFragment?) {
+ val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+ currentDialog?.dismissAllowingStateLoss()
+ newDialog?.show(fragmentManager!!, TAG_DIALOG)
+ }
+
+ fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+ super.setResult(resultCode, data)
+ if (shouldFinish) {
+ finish()
+ }
+ }
+
+ override fun onPositiveResponse(keepData: Boolean) {
+ uninstallViewModel!!.initiateUninstall(keepData)
+ }
+
+ override fun onNegativeResponse() {
+ uninstallViewModel!!.cancelInstall()
+ setResult(Activity.RESULT_FIRST_USER, null, true)
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
index 6d6fcc9..679f696 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
@@ -24,8 +24,8 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index 4cdce52..49901de 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -25,7 +25,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 6398aef..25363d0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -28,7 +28,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
index d45cd76..4667a7a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
@@ -28,7 +28,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
+import com.android.packageinstaller.v2.model.InstallFailed;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
index 9f60f96..7327b5d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
@@ -25,7 +25,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
+import com.android.packageinstaller.v2.model.InstallInstalling;
/**
* Dialog to show when an install is in progress.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
index ab6a932..b2a65faa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
@@ -29,8 +29,8 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallSuccess;
import com.android.packageinstaller.v2.ui.InstallActionListener;
import java.util.List;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
index 47fd67f..58b8b2d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
@@ -24,7 +24,7 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.model.InstallStage;
import com.android.packageinstaller.v2.ui.InstallActionListener;
public class SimpleErrorFragment extends DialogFragment {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
index 1b0885e..32ac4a6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
@@ -30,7 +30,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired;
import com.android.packageinstaller.v2.ui.UninstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
index 305daba..eb7183d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
@@ -25,7 +25,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
+import com.android.packageinstaller.v2.model.UninstallAborted;
import com.android.packageinstaller.v2.ui.UninstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
index 23cc421..835efc6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
@@ -22,7 +22,7 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
+import com.android.packageinstaller.v2.model.UninstallUninstalling;
/**
* Dialog to show that the app is uninstalling.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
deleted file mode 100644
index 04a0622..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
+++ /dev/null
@@ -1,105 +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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-
-
-public class InstallViewModel extends AndroidViewModel {
-
- private static final String TAG = InstallViewModel.class.getSimpleName();
- private final InstallRepository mRepository;
- private final MediatorLiveData<InstallStage> mCurrentInstallStage = new MediatorLiveData<>(
- new InstallStaging());
-
- public InstallViewModel(@NonNull Application application, InstallRepository repository) {
- super(application);
- mRepository = repository;
- }
-
- public MutableLiveData<InstallStage> getCurrentInstallStage() {
- return mCurrentInstallStage;
- }
-
- public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
- InstallStage stage = mRepository.performPreInstallChecks(intent, callerInfo);
- if (stage.getStageCode() == InstallStage.STAGE_ABORTED) {
- mCurrentInstallStage.setValue(stage);
- } else {
- // Since staging is an async operation, we will get the staging result later in time.
- // Result of the file staging will be set in InstallRepository#mStagingResult.
- // As such, mCurrentInstallStage will need to add another MutableLiveData
- // as a data source
- mRepository.stageForInstall();
- mCurrentInstallStage.addSource(mRepository.getStagingResult(), installStage -> {
- if (installStage.getStageCode() != InstallStage.STAGE_READY) {
- mCurrentInstallStage.setValue(installStage);
- } else {
- checkIfAllowedAndInitiateInstall();
- }
- });
- }
- }
-
- public MutableLiveData<Integer> getStagingProgress() {
- return mRepository.getStagingProgress();
- }
-
- private void checkIfAllowedAndInitiateInstall() {
- InstallStage stage = mRepository.requestUserConfirmation();
- mCurrentInstallStage.setValue(stage);
- }
-
- public void forcedSkipSourceCheck() {
- InstallStage stage = mRepository.forcedSkipSourceCheck();
- mCurrentInstallStage.setValue(stage);
- }
-
- public void cleanupInstall() {
- mRepository.cleanupInstall();
- }
-
- public void reattemptInstall() {
- InstallStage stage = mRepository.reattemptInstall();
- mCurrentInstallStage.setValue(stage);
- }
-
- public void initiateInstall() {
- // Since installing is an async operation, we will get the install result later in time.
- // Result of the installation will be set in InstallRepository#mInstallResult.
- // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
- mRepository.initiateInstall();
- mCurrentInstallStage.addSource(mRepository.getInstallResult(), installStage -> {
- if (installStage != null) {
- mCurrentInstallStage.setValue(installStage);
- }
- });
- }
-
- public int getStagedSessionId() {
- return mRepository.getStagedSessionId();
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
new file mode 100644
index 0000000..072fb2d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallStaging
+
+class InstallViewModel(application: Application, val repository: InstallRepository) :
+ AndroidViewModel(application) {
+
+ companion object {
+ private val LOG_TAG = InstallViewModel::class.java.simpleName
+ }
+
+ private val _currentInstallStage = MediatorLiveData<InstallStage>(InstallStaging())
+ val currentInstallStage: MutableLiveData<InstallStage>
+ get() = _currentInstallStage
+
+ fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
+ val stage = repository.performPreInstallChecks(intent, callerInfo)
+ if (stage.stageCode == InstallStage.STAGE_ABORTED) {
+ _currentInstallStage.value = stage
+ } else {
+ // Since staging is an async operation, we will get the staging result later in time.
+ // Result of the file staging will be set in InstallRepository#mStagingResult.
+ // As such, mCurrentInstallStage will need to add another MutableLiveData
+ // as a data source
+ repository.stageForInstall()
+ _currentInstallStage.addSource(repository.stagingResult) { installStage: InstallStage ->
+ if (installStage.stageCode != InstallStage.STAGE_READY) {
+ _currentInstallStage.value = installStage
+ } else {
+ checkIfAllowedAndInitiateInstall()
+ }
+ }
+ }
+ }
+
+ val stagingProgress: LiveData<Int>
+ get() = repository.stagingProgress
+
+ private fun checkIfAllowedAndInitiateInstall() {
+ val stage = repository.requestUserConfirmation()
+ _currentInstallStage.value = stage
+ }
+
+ fun forcedSkipSourceCheck() {
+ val stage = repository.forcedSkipSourceCheck()
+ _currentInstallStage.value = stage
+ }
+
+ fun cleanupInstall() {
+ repository.cleanupInstall()
+ }
+
+ fun reattemptInstall() {
+ val stage = repository.reattemptInstall()
+ _currentInstallStage.value = stage
+ }
+
+ fun initiateInstall() {
+ // Since installing is an async operation, we will get the install result later in time.
+ // Result of the installation will be set in InstallRepository#mInstallResult.
+ // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
+ repository.initiateInstall()
+ _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? ->
+ if (installStage != null) {
+ _currentInstallStage.value = installStage
+ }
+ }
+ }
+
+ val stagedSessionId: Int
+ get() = repository.stagedSessionId
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
deleted file mode 100644
index ef459e6..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
+++ /dev/null
@@ -1,45 +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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.InstallRepository;
-
-public class InstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
- private final InstallRepository mRepository;
- private final Application mApplication;
-
- public InstallViewModelFactory(Application application, InstallRepository repository) {
- // Calling super class' ctor ensures that create method is called correctly and the right
- // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
- // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
- super(application);
- mApplication = application;
- mRepository = repository;
- }
-
- @NonNull
- @Override
- @SuppressWarnings("unchecked")
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
- return (T) new InstallViewModel(mApplication, mRepository);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
new file mode 100644
index 0000000..07b2f4f
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.InstallRepository
+
+class InstallViewModelFactory(val application: Application, val repository: InstallRepository) :
+ ViewModelProvider.AndroidViewModelFactory(application) {
+
+ // Calling super class' ctor ensures that create method is called correctly and the right
+ // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
+ // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return InstallViewModel(application, repository) as T
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
deleted file mode 100644
index 3f7bce8..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
+++ /dev/null
@@ -1,69 +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
- *
- * https://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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-
-public class UninstallViewModel extends AndroidViewModel {
-
- private static final String TAG = UninstallViewModel.class.getSimpleName();
- private final UninstallRepository mRepository;
- private final MediatorLiveData<UninstallStage> mCurrentUninstallStage =
- new MediatorLiveData<>();
-
- public UninstallViewModel(@NonNull Application application, UninstallRepository repository) {
- super(application);
- mRepository = repository;
- }
-
- public MutableLiveData<UninstallStage> getCurrentUninstallStage() {
- return mCurrentUninstallStage;
- }
-
- public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
- UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo);
- if (stage.getStageCode() != UninstallStage.STAGE_ABORTED) {
- stage = mRepository.generateUninstallDetails();
- }
- mCurrentUninstallStage.setValue(stage);
- }
-
- public void initiateUninstall(boolean keepData) {
- mRepository.initiateUninstall(keepData);
- // Since uninstall is an async operation, we will get the uninstall result later in time.
- // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
- // As such, mCurrentUninstallStage will need to add another MutableLiveData
- // as a data source
- mCurrentUninstallStage.addSource(mRepository.getUninstallResult(), uninstallStage -> {
- if (uninstallStage != null) {
- mCurrentUninstallStage.setValue(uninstallStage);
- }
- });
- }
-
- public void cancelInstall() {
- mRepository.cancelInstall();
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
new file mode 100644
index 0000000..80886e9
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+
+class UninstallViewModel(application: Application, val repository: UninstallRepository) :
+ AndroidViewModel(application) {
+
+ companion object {
+ private val LOG_TAG = UninstallViewModel::class.java.simpleName
+ }
+
+ private val _currentUninstallStage = MediatorLiveData<UninstallStage>()
+ val currentUninstallStage: MutableLiveData<UninstallStage>
+ get() = _currentUninstallStage
+
+ fun preprocessIntent(intent: Intent, callerInfo: UninstallRepository.CallerInfo) {
+ var stage = repository.performPreUninstallChecks(intent, callerInfo)
+ if (stage.stageCode != UninstallStage.STAGE_ABORTED) {
+ stage = repository.generateUninstallDetails()
+ }
+ _currentUninstallStage.value = stage
+ }
+
+ fun initiateUninstall(keepData: Boolean) {
+ repository.initiateUninstall(keepData)
+ // Since uninstall is an async operation, we will get the uninstall result later in time.
+ // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
+ // As such, _currentUninstallStage will need to add another MutableLiveData
+ // as a data source
+ _currentUninstallStage.addSource(repository.uninstallResult) { uninstallStage: UninstallStage? ->
+ if (uninstallStage != null) {
+ _currentUninstallStage.value = uninstallStage
+ }
+ }
+ }
+
+ fun cancelInstall() {
+ repository.cancelInstall()
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
deleted file mode 100644
index cd9845e..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
+++ /dev/null
@@ -1,46 +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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-
-public class UninstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
- private final UninstallRepository mRepository;
- private final Application mApplication;
-
- public UninstallViewModelFactory(Application application, UninstallRepository repository) {
- // Calling super class' ctor ensures that create method is called correctly and the right
- // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
- // UninstallViewModel(application) is used, and repository isn't initialized in
- // the viewmodel
- super(application);
- mApplication = application;
- mRepository = repository;
- }
-
- @NonNull
- @Override
- @SuppressWarnings("unchecked")
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
- return (T) new UninstallViewModel(mApplication, mRepository);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
new file mode 100644
index 0000000..0a316e7
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallRepository
+
+class UninstallViewModelFactory(val application: Application, val repository: UninstallRepository) :
+ ViewModelProvider.AndroidViewModelFactory(application) {
+
+ // Calling super class' ctor ensures that create method is called correctly and the right
+ // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
+ // UninstallViewModel(application) is used, and repository isn't initialized in
+ // the viewmodel
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return UninstallViewModel(application, repository) as T
+ }
+}
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index df72a92..044ba87 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -15,9 +15,12 @@
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
- "SettingsLibTile"
+ "androidx.annotation_annotation",
+ "SettingsLibTile",
],
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2501869..ffe3d1d 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -60,6 +60,9 @@
"src/**/*.java",
"src/**/*.kt",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
index 986baf7..bd0dbdc 100644
--- a/packages/SettingsLib/EmergencyNumber/Android.bp
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -17,4 +17,7 @@
],
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce..d9f74da 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -28,4 +28,7 @@
"com.android.extservices",
"com.android.healthfitness",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 028489d..3b04bd9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -30,4 +30,7 @@
"//apex_available:platform",
"com.android.permission",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 2d93e4e..22e4e94 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -14,9 +14,12 @@
srcs: ["src/**/*.java"],
static_libs: [
- "androidx.annotation_annotation",
+ "androidx.annotation_annotation",
],
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c07a802..c385d38 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -15,4 +15,7 @@
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index eb9e329..19c59dd 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -14,8 +14,11 @@
srcs: ["src/**/*.java"],
static_libs: [
- "androidx.annotation_annotation",
+ "androidx.annotation_annotation",
],
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index c7ad24c..d5a56c8 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -31,4 +31,7 @@
"com.android.healthfitness",
"com.android.permission",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 202e7fe..3b14712 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,6 +12,9 @@
visibility: ["//visibility:private"],
srcs: ["interface-src/**/*.java"],
host_supported: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
@@ -24,6 +27,9 @@
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_plugin {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index e5dbe5f..d6e8d26 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -112,5 +112,7 @@
Settings.Global.Wearable.SCREENSHOT_ENABLED,
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
+ Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+ Settings.Global.FORCE_ENABLE_PSS_PROFILING,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 3027c5f..f8bdcf6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -447,5 +447,7 @@
VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 0000000..472484a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+ viewModel: KeyguardLongPressViewModel,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+ val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+ val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ enabled = isEnabled,
+ onLongClick = viewModel::onLongPress,
+ onClick = {},
+ interactionSource = interactionSource,
+ // Passing null for the indication removes the ripple effect.
+ indication = null,
+ )
+ .pointerInput(settingsMenuBounds) {
+ awaitEachGesture {
+ val pointerInputChange = awaitFirstDown()
+ if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+ viewModel.onTouchedOutside()
+ }
+ }
+ },
+ ) {
+ content(setSettingsMenuBounds)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec..67a6820 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalFoundationApi::class)
-
package com.android.systemui.keyguard.ui.composable
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@
constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
- @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
private val lockscreenContent: Lazy<LockscreenContent>,
+ private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
@@ -93,9 +72,8 @@
modifier: Modifier,
) {
LockscreenScene(
- viewProvider = viewProvider,
- viewModel = viewModel,
lockscreenContent = lockscreenContent,
+ viewBasedLockscreenContent = viewBasedLockscreenContent,
modifier = modifier,
)
}
@@ -116,98 +94,21 @@
@Composable
private fun SceneScope.LockscreenScene(
- viewProvider: () -> View,
- viewModel: LockscreenSceneViewModel,
lockscreenContent: Lazy<LockscreenContent>,
+ viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
modifier: Modifier = Modifier,
) {
- fun findSettingsMenu(): View {
- return viewProvider().requireViewById(R.id.keyguard_settings_button)
- }
-
- Box(
- modifier = modifier,
- ) {
- LongPressSurface(
- viewModel = viewModel.longPress,
- isSettingsMenuVisible = { findSettingsMenu().isVisible },
- settingsMenuBounds = {
- val bounds = android.graphics.Rect()
- findSettingsMenu().getHitRect(bounds)
- bounds.toComposeRect()
- },
- modifier = Modifier.fillMaxSize(),
- )
-
- if (UseLockscreenContent) {
- lockscreenContent
- .get()
- .Content(
- modifier = Modifier.fillMaxSize(),
- )
- } else {
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy
- // code just in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- modifier = Modifier.fillMaxSize(),
+ if (UseLockscreenContent) {
+ lockscreenContent
+ .get()
+ .Content(
+ modifier = modifier.fillMaxSize(),
+ )
+ } else {
+ with(viewBasedLockscreenContent.get()) {
+ Content(
+ modifier = modifier.fillMaxSize(),
)
}
-
- val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
- Layout(
- modifier = Modifier.fillMaxSize(),
- content = {
- NotificationStack(
- viewModel = viewModel.notifications,
- isScrimVisible = false,
- )
- }
- ) { measurables, constraints ->
- check(measurables.size == 1)
- val height = notificationStackPosition.height.toInt()
- val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
- val placeable = measurables[0].measure(childConstraints)
- layout(constraints.maxWidth, constraints.maxHeight) {
- val start = (constraints.maxWidth - placeable.measuredWidth) / 2
- placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
- }
- }
}
}
-
-@Composable
-private fun LongPressSurface(
- viewModel: KeyguardLongPressViewModel,
- isSettingsMenuVisible: () -> Boolean,
- settingsMenuBounds: () -> Rect,
- modifier: Modifier = Modifier,
-) {
- val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
- Box(
- modifier =
- modifier
- .combinedClickable(
- enabled = isEnabled,
- onLongClick = viewModel::onLongPress,
- onClick = {},
- )
- .pointerInput(Unit) {
- awaitEachGesture {
- val pointerInputChange = awaitFirstDown()
- if (
- isSettingsMenuVisible() &&
- !settingsMenuBounds().contains(pointerInputChange.position)
- ) {
- viewModel.onTouchedOutside()
- }
- }
- },
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 0000000..976161b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.systemui.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+ private val viewModel: LockscreenSceneViewModel,
+ @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+ @Composable
+ fun SceneScope.Content(
+ modifier: Modifier = Modifier,
+ ) {
+ fun findSettingsMenu(): View {
+ return viewProvider().requireViewById(R.id.keyguard_settings_button)
+ }
+
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy
+ // code just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+
+ val notificationStackPosition by
+ viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+ Layout(
+ modifier =
+ Modifier.fillMaxSize().onPlaced {
+ val settingsMenuView = findSettingsMenu()
+ onSettingsMenuPlaced(
+ if (settingsMenuView.isVisible) {
+ val bounds = Rect()
+ settingsMenuView.getHitRect(bounds)
+ bounds.toComposeRect()
+ } else {
+ null
+ }
+ )
+ },
+ content = {
+ NotificationStack(
+ viewModel = viewModel.notifications,
+ isScrimVisible = false,
+ )
+ }
+ ) { measurables, constraints ->
+ check(measurables.size == 1)
+ val height = notificationStackPosition.height.toInt()
+ val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+ val placeable = measurables[0].measure(childConstraints)
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+ placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf..86124c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
/** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "communal"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): communal blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): communal blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7314453..d9d98cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "default"
@@ -59,102 +63,116 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- with(lockSection) { LockIcon() }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
- }
-
- // Aligned to bottom and NOT constrained by the lock icon.
- with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
- }
- },
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
modifier = modifier,
- ) { measurables, constraints ->
- check(measurables.size == 5)
- val (
- aboveLockIconMeasurable,
- lockIconMeasurable,
- belowLockIconMeasurable,
- startShortcutMeasurable,
- endShortcutMeasurable,
- ) = measurables
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
+ with(lockSection) { LockIcon() }
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
- )
- val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
- val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
- startShortcutPleaceable.place(
- x = 0,
- y = constraints.maxHeight - startShortcutPleaceable.height,
- )
- endShortcutPleaceable.place(
- x = constraints.maxWidth - endShortcutPleaceable.width,
- y = constraints.maxHeight - endShortcutPleaceable.height,
- )
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4c119c7..4704f5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -59,105 +63,123 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- // Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
-
- with(lockSection) { LockIcon() }
-
- // Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
- }
- },
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
modifier = modifier,
- ) { measurables, constraints ->
- check(measurables.size == 5)
- val (
- aboveLockIconMeasurable,
- startSideShortcutMeasurable,
- lockIconMeasurable,
- endSideShortcutMeasurable,
- belowLockIconMeasurable,
- ) = measurables
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
+ // Constrained to the left of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
+ with(lockSection) { LockIcon() }
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
- val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
- )
+ // Constrained to the right of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- startSideShortcutPlaceable.placeRelative(
- x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- endSideShortcutPlaceable.placeRelative(
- x =
- lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
- endSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val startSideShortcutMeasurable = measurables[1]
+ val lockIconMeasurable = measurables[2]
+ val endSideShortcutMeasurable = measurables[3]
+ val belowLockIconMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val startSideShortcutPlaceable =
+ startSideShortcutMeasurable.measure(noMinConstraints)
+ val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ startSideShortcutPlaceable.placeRelative(
+ x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ endSideShortcutPlaceable.placeRelative(
+ x =
+ lockIconBounds.right +
+ (constraints.maxWidth - lockIconBounds.right) / 2 -
+ endSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5f..fdf1166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@
* Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
* tablet form factor).
*/
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): split shade blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index d93863d..2a6bea7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -19,25 +19,33 @@
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
import javax.inject.Inject
class LockSection
@@ -46,48 +54,70 @@
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
+ private val lockIconViewController: Lazy<LockIconViewController>,
+ private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+ private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+ private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
) {
@Composable
fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
- MovableElement(
- key = LockIconElementKey,
- modifier = modifier,
- ) {
- val context = LocalContext.current
- Box(
- modifier =
- Modifier.background(Color.Red).layout { measurable, _ ->
- val lockIconBounds = lockIconBounds(context)
- val placeable =
- measurable.measure(
- Constraints.fixed(
- width = lockIconBounds.width,
- height = lockIconBounds.height,
- )
- )
- layout(
- width = placeable.width,
- height = placeable.height,
- alignmentLines =
- mapOf(
- BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
- BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
- BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
- BlueprintAlignmentLines.LockIcon.Bottom to
- lockIconBounds.bottom,
- ),
- ) {
- placeable.place(0, 0)
- }
- },
- ) {
- Text(
- text = "TODO(b/316211368): Lock",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
+ if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+ return
}
+
+ val context = LocalContext.current
+
+ AndroidView(
+ factory = { context ->
+ val view =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ DeviceEntryIconView(context, null).apply {
+ id = R.id.device_entry_icon_view
+ DeviceEntryIconViewBinder.bind(
+ this,
+ deviceEntryIconViewModel.get(),
+ deviceEntryForegroundViewModel.get(),
+ deviceEntryBackgroundViewModel.get(),
+ falsingManager.get(),
+ vibratorHelper.get(),
+ )
+ }
+ } else {
+ // keyguardBottomAreaRefactor()
+ LockIconView(context, null).apply {
+ id = R.id.lock_icon_view
+ lockIconViewController.get().setLockIconView(this)
+ }
+ }
+ view
+ },
+ modifier =
+ modifier.element(LockIconElementKey).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ )
}
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f135be2..c547e2b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,36 +16,25 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
-class NotificationSection @Inject constructor() {
+class NotificationSection
+@Inject
+constructor(
+ private val viewModel: NotificationsPlaceholderViewModel,
+) {
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
- MovableElement(
- key = NotificationsElementKey,
+ NotificationStack(
+ viewModel = viewModel,
+ isScrimVisible = false,
modifier = modifier,
- ) {
- Box(
- modifier = Modifier.fillMaxSize().background(Color.Yellow),
- ) {
- Text(
- text = "TODO(b/316211368): Notifications",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
+ )
}
}
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 0000000..44b0535
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.systemui.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+ private val viewModel: KeyguardSettingsMenuViewModel,
+ private val longPressViewModel: KeyguardLongPressViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val activityStarter: ActivityStarter,
+) {
+ @Composable
+ @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+ fun SettingsMenu(
+ onPlaced: (Rect?) -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ val (disposableHandle, setDisposableHandle) =
+ remember { mutableStateOf<DisposableHandle?>(null) }
+ AndroidView(
+ factory = { context ->
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_settings_popup_menu,
+ null,
+ )
+ .apply {
+ isVisible = false
+ alpha = 0f
+
+ setDisposableHandle(
+ KeyguardSettingsViewBinder.bind(
+ view = this,
+ viewModel = viewModel,
+ longPressViewModel = longPressViewModel,
+ rootViewModel = null,
+ vibratorHelper = vibratorHelper,
+ activityStarter = activityStarter,
+ )
+ )
+ }
+ },
+ onRelease = { disposableHandle?.dispose() },
+ modifier =
+ modifier
+ .padding(
+ bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+ )
+ .padding(
+ horizontal =
+ dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+ )
+ .onPlaced { coordinates ->
+ onPlaced(
+ if (!coordinates.size.toSize().isEmpty()) {
+ Rect(coordinates.positionInParent(), coordinates.size.toSize())
+ } else {
+ null
+ }
+ )
+ },
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 26da1f0..4b21105 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -47,7 +47,7 @@
suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = if (enabled) 1 else 0,
)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8..754d5dc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@
): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun set(
+ suspend fun setInt(
name: String,
value: Int,
)
- suspend fun get(
+ suspend fun getInt(
name: String,
defaultValue: Int = 0,
): Int
+
+ suspend fun getString(name: String): String?
}
class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@
.flowOn(backgroundDispatcher)
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
withContext(backgroundDispatcher) {
Settings.Secure.putInt(
contentResolver,
@@ -90,7 +92,7 @@
}
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return withContext(backgroundDispatcher) {
Settings.Secure.getInt(
contentResolver,
@@ -99,4 +101,13 @@
)
}
}
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(
+ contentResolver,
+ name,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a07..37b9792 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@
return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return settings.value[name]?.toInt() ?: defaultValue
}
+
+ override suspend fun getString(name: String): String? {
+ return settings.value[name]
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index da97a12..1c1335f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -923,14 +923,14 @@
doReturn(500).when(mResources)
.getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
.physical_fingerprint_sensor_center_screen_location_y));
- mAuthController.onConfigurationChanged(null /* newConfig */);
+ mAuthController.onConfigChanged(null /* newConfig */);
final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
doReturn(1000).when(mResources)
.getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
.physical_fingerprint_sensor_center_screen_location_y));
- mAuthController.onConfigurationChanged(null /* newConfig */);
+ mAuthController.onConfigChanged(null /* newConfig */);
assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
}
diff --git a/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
new file mode 100644
index 0000000..2dce37d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners
+ android:topLeftRadius="28dp"
+ android:topRightRadius="28dp"/>
+ <solid android:color="?android:attr/colorBackground" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 8d7f7eb..a71782b 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -22,7 +22,7 @@
android:orientation="vertical"
android:paddingHorizontal="@dimen/dialog_side_padding"
android:paddingTop="@dimen/dialog_top_padding"
- android:background="@*android:drawable/bottomsheet_background"
+ android:background="@drawable/connected_display_dialog_bg"
android:paddingBottom="@dimen/dialog_bottom_padding">
<ImageView
@@ -40,7 +40,7 @@
android:id="@+id/connected_display_dialog_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+ android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/connected_display_dialog_start_mirroring"
android:textAppearance="@style/TextAppearance.Dialog.Title" />
@@ -51,13 +51,14 @@
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
+ android:layout_marginTop="16dp"
android:text="@string/connected_display_dialog_dual_display_stop_warning"
android:textAppearance="@style/TextAppearance.Dialog.Body" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+ android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b25a7..e7eb984 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3265,7 +3265,7 @@
<!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
<string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
<!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
- <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
+ <string name="connected_display_dialog_dual_display_stop_warning">Your inner display will be mirrored. Your front display will be turned off.</string>
<!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="mirror_display">Mirror display</string>
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 5b59e7d..2b41178 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,6 +34,9 @@
srcs: [
":statslog-SystemUI-java-gen",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
@@ -70,6 +73,9 @@
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
kotlincflags: ["-Xjvm-default=all"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -81,6 +87,9 @@
static_kotlin_stdlib: false,
java_version: "1.8",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -100,4 +109,7 @@
},
java_version: "1.8",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 92f66902..387f2e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -101,7 +101,7 @@
}
/** Alerts listener and plugin that the plugin has been created. */
- public void onCreate() {
+ public synchronized void onCreate() {
boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
@@ -128,7 +128,7 @@
}
/** Alerts listener and plugin that the plugin is being shutdown. */
- public void onDestroy() {
+ public synchronized void onDestroy() {
logDebug("onDestroy");
unloadPlugin();
mListener.onPluginDetached(this);
@@ -143,12 +143,13 @@
/**
* Loads and creates the plugin if it does not exist.
*/
- public void loadPlugin() {
+ public synchronized void loadPlugin() {
if (mPlugin != null) {
logDebug("Load request when already loaded");
return;
}
+ // Both of these calls take about 1 - 1.5 seconds in test runs
mPlugin = mPluginFactory.createPlugin();
mPluginContext = mPluginFactory.createPluginContext();
if (mPlugin == null || mPluginContext == null) {
@@ -171,7 +172,7 @@
*
* This will free the associated memory if there are not other references.
*/
- public void unloadPlugin() {
+ public synchronized void unloadPlugin() {
if (mPlugin == null) {
logDebug("Unload request when already unloaded");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index c07a4d2..7295936 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -16,8 +16,6 @@
package com.android.systemui;
-import android.content.res.Configuration;
-
import androidx.annotation.NonNull;
import java.io.PrintWriter;
@@ -42,13 +40,6 @@
/** Main entry point for implementations. Called shortly after SysUI startup. */
void start();
- /** Called when the device configuration changes. This will not be called before
- * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
- *
- * @see android.app.Application#onConfigurationChanged(Configuration) */
- default void onConfigurationChanged(Configuration newConfig) {
- }
-
@Override
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 008de43..e03c627 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -89,6 +89,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.settings.SecureSettings;
@@ -109,7 +110,8 @@
* for antialiasing and emulation purposes.
*/
@SysUISingleton
-public class ScreenDecorations implements CoreStartable, Dumpable {
+public class ScreenDecorations implements
+ CoreStartable, ConfigurationController.ConfigurationListener, Dumpable {
private static final boolean DEBUG_LOGGING = false;
private static final String TAG = "ScreenDecorations";
@@ -575,7 +577,7 @@
if (mPendingManualConfigUpdate) {
mPendingManualConfigUpdate = false;
- onConfigurationChanged(mContext.getResources().getConfiguration());
+ onConfigChanged(mContext.getResources().getConfiguration());
}
}
}
@@ -1062,7 +1064,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
Log.i(TAG, "ScreenDecorations is disabled");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
new file mode 100644
index 0000000..044312b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.systemui
+
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ScreenDecorationsModule {
+ /** Start ScreenDecorations. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenDecorations::class)
+ fun bindScreenDecorationsCoreStartable(impl: ScreenDecorations): CoreStartable
+
+ /** Listen to config changes for ScreenDecorations. */
+ @Binds
+ @IntoSet
+ fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c3f6480..01f6971 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,11 +37,11 @@
import android.view.ThreadedRenderer;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.NotificationChannels;
@@ -354,19 +354,6 @@
}
configController.onConfigurationChanged(newConfig);
Trace.endSection();
- int len = mServices.length;
- for (int i = 0; i < len; i++) {
- if (mServices[i] != null) {
- if (Trace.isEnabled()) {
- Trace.traceBegin(
- Trace.TRACE_TAG_APP,
- mServices[i].getClass().getSimpleName()
- + ".onConfigurationChanged()");
- }
- mServices[i].onConfigurationChanged(newConfig);
- Trace.endSection();
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7a8161e..da49201 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import android.accessibilityservice.AccessibilityService;
@@ -57,6 +58,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
@@ -71,7 +73,7 @@
* Class to register system actions with accessibility framework.
*/
@SysUISingleton
-public class SystemActions implements CoreStartable {
+public class SystemActions implements CoreStartable, ConfigurationController.ConfigurationListener {
private static final String TAG = "SystemActions";
/**
@@ -234,7 +236,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
if (!locale.equals(mLocale)) {
mLocale = locale;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
new file mode 100644
index 0000000..4d6d784
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.accessibility
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface SystemActionsModule {
+ /** Start SystemActions. */
+ @Binds
+ @IntoMap
+ @ClassKey(SystemActions::class)
+ fun bindSystemActionsStartable(sysui: SystemActions): CoreStartable
+
+ /** Listen to config changes for SystemActions. */
+ @Binds @IntoSet fun bindSystemActionsConfigChanges(sysui: SystemActions): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0bd4859..dde9f48 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1303,7 +1303,7 @@
} else if (id == R.id.close_button) {
setEditMagnifierSizeMode(false);
} else {
- animateBounceEffect();
+ animateBounceEffectIfNeeded();
}
}
@@ -1465,7 +1465,12 @@
mBounceEffectDuration = duration;
}
- private void animateBounceEffect() {
+ private void animateBounceEffectIfNeeded() {
+ if (mMirrorView == null) {
+ // run the animation only if the mirror view is not null
+ return;
+ }
+
final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1, mBounceEffectAnimationScale, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127..49e0df6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
-class DismissAnimationController {
+class DragToInteractAnimationController {
private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@
private float mMinDismissSize;
private float mSizePercent;
- DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
mDismissView = dismissView;
mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeUpMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cec..a270558 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DismissAnimationController.DismissCallback mDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@
}
void setDismissCallback(
- DismissAnimationController.DismissCallback dismissCallback) {
+ DragToInteractAnimationController.DismissCallback dismissCallback) {
mDismissCallback = dismissCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f..52e7b91 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private Optional<Runnable> mOnActionDownEnd = Optional.empty();
MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
- DismissAnimationController dismissAnimationController) {
+ DragToInteractAnimationController dragToInteractAnimationController) {
mMenuAnimationController = menuAnimationController;
- mDismissAnimationController = dismissAnimationController;
+ mDragToInteractAnimationController = dragToInteractAnimationController;
}
@Override
@@ -67,7 +67,7 @@
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
- mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+ mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
mOnActionDownEnd.ifPresent(Runnable::run);
break;
@@ -78,9 +78,10 @@
mMenuAnimationController.onDraggingStart();
}
- mDismissAnimationController.showDismissView(/* show= */ true);
+ mDragToInteractAnimationController.showDismissView(/* show= */ true);
- if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+ motionEvent)) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@
mIsDragging = false;
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
mMenuAnimationController.fadeOutIfEnabled();
return true;
}
- if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+ motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index ff3a9e3..62d5feb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final SecureSettings mSecureSettings;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private final MenuViewModel mMenuViewModel;
private final Observer<Boolean> mDockTooltipObserver =
this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
- mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
hideMenuAndShowMessage();
mDismissView.hide();
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
});
mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
mMenuView.setMoveToTuckedListener(this);
@@ -243,7 +244,7 @@
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
mDismissView.updateResources();
- mDismissAnimationController.updateResources();
+ mDragToInteractAnimationController.updateResources();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 5fba761..8a1a2da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -84,6 +84,7 @@
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
@@ -114,8 +115,12 @@
* {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
-public class AuthController implements CoreStartable, CommandQueue.Callbacks,
- AuthDialogCallback, DozeReceiver {
+public class AuthController implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks,
+ AuthDialogCallback,
+ DozeReceiver {
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
@@ -1297,7 +1302,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
updateSensorLocations();
// TODO(b/287311775): consider removing this to retain the UI cleanly vs re-creating
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 8ae6f87..307b985 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -19,6 +19,7 @@
import android.content.res.Resources
import com.android.internal.R
import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
@@ -38,18 +39,30 @@
import com.android.systemui.biometrics.udfps.OverlapDetector
import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.ThreadFactory
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.util.concurrent.Executor
import javax.inject.Qualifier
/** Dagger module for all things biometric. */
@Module
interface BiometricsModule {
+ /** Starts AuthController. */
+ @Binds
+ @IntoMap
+ @ClassKey(AuthController::class)
+ fun bindAuthControllerStartable(service: AuthController): CoreStartable
+
+ /** Listen to config changes for AuthController. */
+ @Binds
+ @IntoSet
+ fun bindAuthControllerConfigChanges(service: AuthController): ConfigurationListener
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index a8c9446..c36e0e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -47,6 +47,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
@@ -59,49 +60,56 @@
constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
- private val biometricStatusInteractor: BiometricStatusInteractor,
- private val displayStateInteractor: DisplayStateInteractor,
- private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
- private val fpsUnlockTracker: FpsUnlockTracker,
- private val layoutInflater: LayoutInflater,
- private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
- private val sfpsSensorInteractor: SideFpsSensorInteractor,
- private val windowManager: WindowManager
+ private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
+ private val displayStateInteractor: Lazy<DisplayStateInteractor>,
+ private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
+ private val fpsUnlockTracker: Lazy<FpsUnlockTracker>,
+ private val layoutInflater: Lazy<LayoutInflater>,
+ private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
+ private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+ private val windowManager: Lazy<WindowManager>
) : CoreStartable {
override fun start() {
if (!SideFpsControllerRefactor.isEnabled) {
return
}
+
applicationScope
.launch {
- combine(
- biometricStatusInteractor.sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
- sideFpsProgressBarViewModel.isVisible,
- ::Triple
- )
- .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
- .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
- val (
- systemServerAuthReason,
- showIndicatorForDeviceEntry,
- progressBarIsVisible) =
- combinedFlows
- if (!isInRearDisplayMode) {
- if (progressBarIsVisible) {
- hide()
- } else if (systemServerAuthReason != NotRunning) {
- show()
- } else if (showIndicatorForDeviceEntry) {
- show()
- } else {
- hide()
+ sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+ if (isSfpsAvailable) {
+ combine(
+ biometricStatusInteractor.get().sfpsAuthenticationReason,
+ deviceEntrySideFpsOverlayInteractor
+ .get()
+ .showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.get().isVisible,
+ ::Triple
+ )
+ .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+ .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+ val (
+ systemServerAuthReason,
+ showIndicatorForDeviceEntry,
+ progressBarIsVisible) =
+ combinedFlows
+ if (!isInRearDisplayMode) {
+ if (progressBarIsVisible) {
+ hide()
+ } else if (systemServerAuthReason != NotRunning) {
+ show()
+ } else if (showIndicatorForDeviceEntry) {
+ show()
+ } else {
+ hide()
+ }
+ }
}
- }
}
+ }
}
- .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+ .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
}
private var overlayView: View? = null
@@ -113,29 +121,29 @@
if (it.isAttachedToWindow) {
lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie?.pauseAnimation()
- windowManager.removeView(it)
+ windowManager.get().removeView(it)
}
}
- overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+ overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
val overlayViewModel =
SideFpsOverlayViewModel(
applicationContext,
- biometricStatusInteractor,
- deviceEntrySideFpsOverlayInteractor,
- displayStateInteractor,
- sfpsSensorInteractor,
- sideFpsProgressBarViewModel
+ biometricStatusInteractor.get(),
+ deviceEntrySideFpsOverlayInteractor.get(),
+ displayStateInteractor.get(),
+ sfpsSensorInteractor.get(),
+ sideFpsProgressBarViewModel.get()
)
- bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+ bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
overlayView!!.visibility = View.INVISIBLE
- windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+ windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
- windowManager.removeView(overlayView)
+ windowManager.get().removeView(overlayView)
overlayView = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 236c5b8..50f861f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -23,6 +23,8 @@
import android.hardware.SensorPrivacyManager;
import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.SystemActionsModule;
import com.android.systemui.battery.BatterySaverModule;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
@@ -34,6 +36,7 @@
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.reardisplay.RearDisplayModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.rotationlock.RotationLockModule;
@@ -59,6 +62,7 @@
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.toast.ToastModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -89,19 +93,23 @@
CollapsedStatusBarFragmentStartableModule.class,
GestureModule.class,
HeadsUpModule.class,
+ KeyboardShortcutsModule.class,
MediaModule.class,
MultiUserUtilsModule.class,
NavigationBarControllerModule.class,
PowerModule.class,
QSModule.class,
- ShadeModule.class,
+ RearDisplayModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
- SceneContainerFrameworkModule.class,
+ ScreenDecorationsModule.class,
+ SystemActionsModule.class,
+ ShadeModule.class,
StartCentralSurfacesModule.class,
+ SceneContainerFrameworkModule.class,
+ ToastModule.class,
VolumeModule.class,
- WallpaperModule.class,
- KeyboardShortcutsModule.class
+ WallpaperModule.class
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d041acb..ac71664 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -19,12 +19,9 @@
import com.android.keyguard.KeyguardBiometricLockoutLogger
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
-import com.android.systemui.ScreenDecorations
import com.android.systemui.SliceBroadcastRelayHandler
-import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
@@ -46,10 +43,6 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.power.PowerUI
-import com.android.systemui.reardisplay.RearDisplayDialogController
-import com.android.systemui.recents.Recents
-import com.android.systemui.recents.ScreenPinningRequest
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -61,11 +54,9 @@
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
-import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
-import com.android.systemui.volume.VolumeUI
import com.android.systemui.wallpapers.dagger.WallpaperModule
import com.android.systemui.wmshell.WMShell
import dagger.Binds
@@ -74,7 +65,12 @@
import dagger.multibindings.IntoMap
/**
- * Collection of {@link CoreStartable}s that should be run on AOSP.
+ * DEPRECATED: DO NOT ADD THINGS TO THIS FILE.
+ *
+ * Add a feature specific daggger module for what you are working on. Bind your CoreStartable there.
+ * Include that module where it is needed.
+ *
+ * @deprecated
*/
@Module(
includes = [
@@ -85,12 +81,6 @@
]
)
abstract class SystemUICoreStartableModule {
- /** Inject into AuthController. */
- @Binds
- @IntoMap
- @ClassKey(AuthController::class)
- abstract fun bindAuthController(service: AuthController): CoreStartable
-
/** Inject into BiometricNotificationService */
@Binds
@IntoMap
@@ -158,18 +148,6 @@
@PerUser
abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
- /** Inject into PowerUI. */
- @Binds
- @IntoMap
- @ClassKey(PowerUI::class)
- abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
-
- /** Inject into Recents. */
- @Binds
- @IntoMap
- @ClassKey(Recents::class)
- abstract fun bindRecents(sysui: Recents): CoreStartable
-
/** Inject into ImmersiveModeConfirmation. */
@Binds
@IntoMap
@@ -182,12 +160,6 @@
@ClassKey(RingtonePlayer::class)
abstract fun bind(sysui: RingtonePlayer): CoreStartable
- /** Inject into ScreenDecorations. */
- @Binds
- @IntoMap
- @ClassKey(ScreenDecorations::class)
- abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
-
/** Inject into GesturePointerEventHandler. */
@Binds
@IntoMap
@@ -218,23 +190,12 @@
@ClassKey(StorageNotification::class)
abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
- /** Inject into SystemActions. */
- @Binds
- @IntoMap
- @ClassKey(SystemActions::class)
- abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
-
/** Inject into ThemeOverlayController. */
@Binds
@IntoMap
@ClassKey(ThemeOverlayController::class)
abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
- /** Inject into ToastUI. */
- @Binds
- @IntoMap
- @ClassKey(ToastUI::class)
- abstract fun bindToastUI(service: ToastUI): CoreStartable
/** Inject into MediaOutputSwitcherDialogUI. */
@Binds
@@ -242,12 +203,6 @@
@ClassKey(MediaOutputSwitcherDialogUI::class)
abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
- /** Inject into VolumeUI. */
- @Binds
- @IntoMap
- @ClassKey(VolumeUI::class)
- abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
-
/** Inject into Magnification. */
@Binds
@IntoMap
@@ -293,11 +248,6 @@
abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
- /** Inject into RearDisplayDialogController) */
- @Binds
- @IntoMap
- @ClassKey(RearDisplayDialogController::class)
- abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
/** Inject into StylusUsiPowerStartable) */
@Binds
@@ -361,9 +311,4 @@
@IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(ScreenPinningRequest::class)
- abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 734a879..1b35005 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -605,11 +605,6 @@
val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
unreleasedFlag("bigpicture_notification_lazy_loading")
- // TODO(b/292062937): Tracking bug
- @JvmField
- val NOTIFICATION_CLEARABLE_REFACTOR =
- unreleasedFlag("notification_clearable_refactor")
-
// TODO(b/283740863): Tracking Bug
@JvmField
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361..cfa5294 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@
SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
}
}
+
+ /** The arrow navigation that was operating the slider has stopped. */
+ fun onArrowUp() {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63..10098fa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@
override suspend fun iterateState(event: SliderEvent) {
when (currentState) {
- SliderState.IDLE -> handleIdle(event.type)
+ SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
SliderState.WAIT -> handleWait(event.type, event.currentProgress)
SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@
SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+ handleArrowContinuous(event.type, event.currentProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
}
latestProgress = event.currentProgress
}
- private fun handleIdle(newEventType: SliderEventType) {
+ private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
timerJob = launchTimer()
// The WAIT state will wait for the timer to complete or a slider progress to occur.
// This will disambiguate between an imprecise touch that acquires the slider handle,
// and a select and jump operation in the slider track.
setState(SliderState.WAIT)
+ } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+ val state =
+ if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVED_ONCE
+ setState(state)
}
}
@@ -176,6 +185,13 @@
SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
SliderState.JUMP_TRACK_LOCATION_SELECTED ->
sliderListener.onProgressJump(latestProgress)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+ executeOnBookend()
+ // This transitory execution must also reset the state
+ resetState()
+ }
else -> {}
}
}
@@ -204,6 +220,43 @@
currentProgress <= config.lowerBookendThreshold
}
+ private fun handleArrowOnce(newEventType: SliderEventType) {
+ val nextState =
+ when (newEventType) {
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+ val reachedBookend = bookendReached(currentProgress)
+ val nextState =
+ when (newEventType) {
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+ if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowBookend() = setState(SliderState.IDLE)
+
@VisibleForTesting
fun setState(state: SliderState) {
currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e277..4a63941 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@
/* The slider has stopped tracking touch events. */
STOPPED_TRACKING_TOUCH,
/* The external (not touch) stimulus that was modifying the slider progress has stopped. */
- EXTERNAL_STIMULUS_RELEASE,
+ ARROW_UP,
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e6..de6ddd7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@
DRAG_HANDLE_REACHED_BOOKEND,
/* A location in the slider track has been selected. */
JUMP_TRACK_LOCATION_SELECTED,
- /* The slider handled moved to a bookend after it was selected. */
+ /* The slider handle moved to a bookend after it was selected. */
JUMP_BOOKEND_SELECTED,
+ /** The slider handle moved due to single select-and-arrow operation */
+ ARROW_HANDLE_MOVED_ONCE,
+ /** The slider handle moves continuously due to constant select-and-arrow operations */
+ ARROW_HANDLE_MOVES_CONTINUOUSLY,
+ /** The slider handle reached a bookend due to a select-and-arrow operation */
+ ARROW_HANDLE_REACHED_BOOKEND,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 4d60dd0..17d7836 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -626,17 +626,19 @@
faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
return
}
- detectCancellationSignal?.cancel()
- detectCancellationSignal = CancellationSignal()
withContext(mainDispatcher) {
// We always want to invoke face detect in the main thread.
faceAuthLogger.faceDetectionStarted()
- faceManager?.detectFace(
- checkNotNull(detectCancellationSignal),
- detectionCallback,
- SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
- .toFaceAuthenticateOptions()
- )
+ detectCancellationSignal?.cancel()
+ detectCancellationSignal = CancellationSignal()
+ detectCancellationSignal?.let {
+ faceManager?.detectFace(
+ it,
+ detectionCallback,
+ SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
+ .toFaceAuthenticateOptions()
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index c98f637..ecf78d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -23,15 +23,18 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/**
* Encapsulates business logic for device entry events that impact the side fingerprint sensor
@@ -41,6 +44,7 @@
class DeviceEntrySideFpsOverlayInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -50,7 +54,13 @@
init {
if (!DeviceEntryUdfpsRefactor.isEnabled) {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ applicationScope.launch {
+ deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType ->
+ if (sensorType == BiometricType.SIDE_FINGERPRINT) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206..96e83b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@
vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
settingsMenu.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = settingsMenu,
viewModel = viewModel.settingsMenuViewModel,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 362e7e6..fad0370 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -255,6 +255,7 @@
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.CONFIRM,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
)
}
}
@@ -264,6 +265,7 @@
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.REJECT,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c..c6dfcb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.view.rawDistanceFrom
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
class KeyguardSettingsButtonOnTouchListener(
- private val view: LaunchableLinearLayout,
private val viewModel: KeyguardSettingsMenuViewModel,
) : View.OnTouchListener {
@@ -41,8 +39,10 @@
MotionEvent.ACTION_UP -> {
view.isPressed = false
val distanceMoved =
- motionEvent
- .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+ motionEvent.rawDistanceFrom(
+ downPositionDisplayCoords.x,
+ downPositionDisplayCoords.y
+ )
val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
viewModel.onTouchGestureEnded(isClick)
if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e7..f67cb68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@
object KeyguardSettingsViewBinder {
fun bind(
- parentView: View,
+ view: View,
viewModel: KeyguardSettingsMenuViewModel,
longPressViewModel: KeyguardLongPressViewModel,
- rootViewModel: KeyguardRootViewModel,
+ rootViewModel: KeyguardRootViewModel?,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
- val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@
vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
view.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = view,
viewModel = viewModel,
)
)
@@ -96,7 +92,7 @@
}
launch {
- rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+ rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
if (view.isVisible) {
val hitRect = Rect()
view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 24240df..940d1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -142,6 +142,11 @@
return true
}
+ if (renderer == null || onDestroy == null) {
+ Log.wtf(TAG, "Renderer/onDestroy should not be null.")
+ return true
+ }
+
when (message.what) {
KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569..36bbe4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@
constructor(
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
+ val longPress: KeyguardLongPressViewModel,
) {
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index e827a1e..3e6d46c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -25,12 +25,12 @@
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.ViewPriority
@@ -162,7 +162,7 @@
logger: MediaTttSenderLogger,
instanceId: InstanceId,
): ChipbarInfo {
- val packageName = checkNotNull(routeInfo.clientPackageName)
+ val packageName = routeInfo.clientPackageName
val otherDeviceName =
if (routeInfo.name.isBlank()) {
context.getString(R.string.media_ttt_default_device_type)
@@ -171,7 +171,7 @@
}
val icon =
MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
- logger.logPackageNotFound(packageName)
+ packageName?.let { logger.logPackageNotFound(it) }
}
val timeout =
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1534653..958ace35 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -48,12 +48,13 @@
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -62,7 +63,10 @@
import javax.inject.Inject;
@SysUISingleton
-public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
+public class PowerUI implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -223,7 +227,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
// Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 7184fa0..8dd0ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,14 +16,19 @@
package com.android.systemui.power.dagger;
+import com.android.systemui.CoreStartable;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
import com.android.systemui.power.data.repository.PowerRepositoryModule;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/** Dagger Module for code in the power package. */
@@ -33,6 +38,17 @@
}
)
public interface PowerModule {
+ /** Starts PowerUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PowerUI.class)
+ CoreStartable bindPowerUIStartable(PowerUI impl);
+
+ /** Listen to config changes for PowerUI. */
+ @Binds
+ @IntoSet
+ ConfigurationController.ConfigurationListener bindPowerUIConfigChanges(PowerUI impl);
+
/** */
@Binds
EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 4b21e44..f071623 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -29,11 +29,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
@@ -57,7 +58,10 @@
*/
@SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
@SysUISingleton
-public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+public class RearDisplayDialogController implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
private int[] mFoldedStates;
private boolean mStartedFolded;
@@ -96,7 +100,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
&& mDialogViewContainer != null) {
// Refresh the dialog view when configuration is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
new file mode 100644
index 0000000..6ab294d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui.reardisplay
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface RearDisplayModule {
+
+ /** Start RearDisplayDialogController. */
+ @Binds
+ @IntoMap
+ @ClassKey(RearDisplayDialogController::class)
+ abstract fun bindRearDisplayDialogControllerStartable(
+ impl: RearDisplayDialogController
+ ): CoreStartable
+
+ /** Listen to config changes for RearDisplayDialogController. */
+ @Binds
+ @IntoSet
+ fun bindRearDisplayDialogControllerConfigChanges(
+ impl: RearDisplayDialogController
+ ): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b041f95..4ee65b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -23,13 +23,17 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
/**
* A proxy to a Recents implementation.
*/
-public class Recents implements CoreStartable, CommandQueue.Callbacks {
+public class Recents implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
private final Context mContext;
private final RecentsImplementation mImpl;
@@ -53,7 +57,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
mImpl.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
index 77a4b9f7..1108917 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
@@ -18,14 +18,17 @@
import android.content.Context;
-import com.android.systemui.res.R;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/**
* Dagger injection module for {@link RecentsImplementation}
@@ -33,6 +36,28 @@
@Module
public abstract class RecentsModule {
+ /** Start Recents. */
+ @Binds
+ @IntoMap
+ @ClassKey(Recents.class)
+ abstract CoreStartable bindRecentsStartable(Recents impl);
+
+ /** Listen to config changes for Recents. */
+ @Binds
+ @IntoSet
+ abstract ConfigurationListener bindRecentsConfigChanges(Recents impl);
+
+ /** Start ScreenPinningRequest. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenPinningRequest.class)
+ abstract CoreStartable bindScreenPinningRequestStartable(ScreenPinningRequest impl);
+
+ /** Listen to config changes for ScreenPinningRequest. */
+ @Binds
+ @IntoSet
+ abstract ConfigurationListener bindScreenPinningRequestConfigChanges(ScreenPinningRequest impl);
+
/**
* @return The {@link RecentsImplementation} from the config.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3e574e7..2b717cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -54,25 +54,29 @@
import androidx.annotation.NonNull;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.leak.RotationUtils;
+import dagger.Lazy;
+
import java.util.ArrayList;
import javax.inject.Inject;
-import dagger.Lazy;
-
@SysUISingleton
-public class ScreenPinningRequest implements View.OnClickListener,
- NavigationModeController.ModeChangedListener, CoreStartable {
+public class ScreenPinningRequest implements
+ View.OnClickListener,
+ NavigationModeController.ModeChangedListener,
+ CoreStartable,
+ ConfigurationController.ConfigurationListener {
private static final String TAG = "ScreenPinningRequest";
private final Context mContext;
@@ -149,7 +153,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (mRequestWindow != null) {
mRequestWindow.onConfigurationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d13edf0..d382b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,8 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,6 +89,17 @@
if (mShadeInteractor.isQsExpanded().getValue()) {
finish();
}
+
+ View view = findViewById(R.id.brightness_mirror_container);
+ if (view != null) {
+ collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange);
+ }
+ }
+
+ private void onShadeStateChange(boolean isQsExpanded) {
+ if (isQsExpanded) {
+ requestFinish();
+ }
}
private void setWindowAttributes() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f..be1fa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@
mListener.onChanged(mTracking, progress, false);
SeekableSliderEventProducer eventProducer =
mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
+ if (eventProducer != null && fromUser) {
eventProducer.onProgressChanged(seekBar, progress, fromUser);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 8fe0022..b76cdb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -237,7 +237,7 @@
// Add and bind.
val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
- for ((idx, notifKey) in toAdd.withIndex()) {
+ for (notifKey in toAdd) {
// Lookup the StatusBarIconView from the store.
val sbiv = viewStore.iconView(notifKey)
if (sbiv == null) {
@@ -256,7 +256,7 @@
// added again.
removeTransientView(sbiv)
}
- view.addView(sbiv, idx)
+ view.addView(sbiv)
boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
boundViewsByNotifKey[notifKey] =
Pair(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 97cb45a..6e8ad2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -21,13 +21,17 @@
import android.os.LocaleList
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import javax.inject.Inject
@SysUISingleton
-class ConfigurationControllerImpl @Inject constructor(context: Context) : ConfigurationController {
+class ConfigurationControllerImpl @Inject constructor(
+ @Application context: Context,
+ ) : ConfigurationController {
- private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
+ private val listeners: MutableList<ConfigurationListener> = ArrayList()
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
@@ -143,14 +147,12 @@
}
}
-
-
- override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+ override fun addCallback(listener: ConfigurationListener) {
listeners.add(listener)
listener.onDensityOrFontScaleChanged()
}
- override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+ override fun removeCallback(listener: ConfigurationListener) {
listeners.remove(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
new file mode 100644
index 0000000..90ebaf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@SysUISingleton
+class ConfigurationControllerStartable
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
+) : CoreStartable {
+ override fun start() {
+ listeners.forEach { configurationController.addCallback(it) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
deleted file mode 100644
index 7048a78..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.UserHandle;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
- */
-public class StatusBarIconHolder {
- public static final int TYPE_ICON = 0;
- /**
- * TODO (b/249790733): address this once the new pipeline is in place
- * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
- * to inform the old view system about changes to the data set (the list of mobile icons). The
- * design of the new pipeline should allow for removal of this icon holder type, and obsolete
- * the need for this entire class.
- *
- * @deprecated This field only exists so the new status bar pipeline can interface with the
- * view holder system.
- */
- @Deprecated
- public static final int TYPE_MOBILE_NEW = 3;
-
- /**
- * TODO (b/238425913): address this once the new pipeline is in place
- * This type exists so that the new wifi pipeline can be used to inform the old view system
- * about the existence of the wifi icon. The design of the new pipeline should allow for removal
- * of this icon holder type, and obsolete the need for this entire class.
- *
- * @deprecated This field only exists so the new status bar pipeline can interface with the
- * view holder system.
- */
- @Deprecated
- public static final int TYPE_WIFI_NEW = 4;
-
- @IntDef({
- TYPE_ICON,
- TYPE_MOBILE_NEW,
- TYPE_WIFI_NEW
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface IconType {}
-
- private StatusBarIcon mIcon;
- private @IconType int mType = TYPE_ICON;
- private int mTag = 0;
-
- /** Returns a human-readable string representing the given type. */
- public static String getTypeString(@IconType int type) {
- switch(type) {
- case TYPE_ICON: return "ICON";
- case TYPE_MOBILE_NEW: return "MOBILE_NEW";
- case TYPE_WIFI_NEW: return "WIFI_NEW";
- default: return "UNKNOWN";
- }
- }
-
- private StatusBarIconHolder() {
- }
-
- public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
- StatusBarIconHolder wrapper = new StatusBarIconHolder();
- wrapper.mIcon = icon;
-
- return wrapper;
- }
-
- /** Creates a new holder with for the new wifi icon. */
- public static StatusBarIconHolder forNewWifiIcon() {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mType = TYPE_WIFI_NEW;
- return holder;
- }
-
- /**
- * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
- * determine icon ordering and building the correct view model
- */
- public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mType = TYPE_MOBILE_NEW;
- holder.mTag = subId;
-
- return holder;
- }
-
- /**
- * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
- */
- public static StatusBarIconHolder fromCallIndicatorState(
- Context context,
- CallIndicatorIconState state) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
- String contentDescription = state.isNoCalling
- ? state.noCallingDescription : state.callStrengthDescription;
- holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource(context, resId), 0, 0, contentDescription);
- holder.mTag = state.subId;
- return holder;
- }
-
- public @IconType int getType() {
- return mType;
- }
-
- @Nullable
- public StatusBarIcon getIcon() {
- return mIcon;
- }
-
- public void setIcon(StatusBarIcon icon) {
- mIcon = icon;
- }
-
- public boolean isVisible() {
- switch (mType) {
- case TYPE_ICON:
- return mIcon.visible;
- case TYPE_MOBILE_NEW:
- case TYPE_WIFI_NEW:
- // The new pipeline controls visibilities via the view model and view binder, so
- // this is effectively an unused return value.
- return true;
- default:
- return true;
- }
- }
-
- public void setVisible(boolean visible) {
- if (isVisible() == visible) {
- return;
- }
-
- switch (mType) {
- case TYPE_ICON:
- mIcon.visible = visible;
- break;
-
- case TYPE_MOBILE_NEW:
- case TYPE_WIFI_NEW:
- // The new pipeline controls visibilities via the view model and view binder, so
- // ignore setVisible.
- break;
- }
- }
-
- public int getTag() {
- return mTag;
- }
-
- @Override
- public String toString() {
- return "StatusBarIconHolder(type=" + getTypeString(mType)
- + " tag=" + getTag()
- + " visible=" + isVisible() + ")";
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
new file mode 100644
index 0000000..5b55a1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.annotation.IntDef
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState
+
+/** Wraps [com.android.internal.statusbar.StatusBarIcon] so we can still have a uniform list */
+class StatusBarIconHolder private constructor() {
+ @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW)
+ @Retention(AnnotationRetention.SOURCE)
+ internal annotation class IconType
+
+ var icon: StatusBarIcon? = null
+
+ @IconType
+ var type = TYPE_ICON
+ private set
+
+ var tag = 0
+ private set
+
+ var isVisible: Boolean
+ get() =
+ when (type) {
+ TYPE_ICON -> icon!!.visible
+
+ // The new pipeline controls visibilities via the view model and
+ // view binder, so
+ // this is effectively an unused return value.
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW -> true
+ else -> true
+ }
+ set(visible) {
+ if (isVisible == visible) {
+ return
+ }
+ when (type) {
+ TYPE_ICON -> icon!!.visible = visible
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW -> {}
+ }
+ }
+
+ override fun toString(): String {
+ return ("StatusBarIconHolder(type=${getTypeString(type)}" +
+ " tag=$tag" +
+ " visible=$isVisible)")
+ }
+
+ companion object {
+ const val TYPE_ICON = 0
+
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place This type exists so
+ * that the new pipeline (see [MobileIconViewModel]) can be used to inform the old view
+ * system about changes to the data set (the list of mobile icons). The design of the new
+ * pipeline should allow for removal of this icon holder type, and obsolete the need for
+ * this entire class.
+ */
+ @Deprecated(
+ """This field only exists so the new status bar pipeline can interface with the
+ view holder system."""
+ )
+ const val TYPE_MOBILE_NEW = 3
+
+ /**
+ * TODO (b/238425913): address this once the new pipeline is in place This type exists so
+ * that the new wifi pipeline can be used to inform the old view system about the existence
+ * of the wifi icon. The design of the new pipeline should allow for removal of this icon
+ * holder type, and obsolete the need for this entire class.
+ */
+ @Deprecated(
+ """This field only exists so the new status bar pipeline can interface with the
+ view holder system."""
+ )
+ const val TYPE_WIFI_NEW = 4
+
+ /** Returns a human-readable string representing the given type. */
+ fun getTypeString(@IconType type: Int): String {
+ return when (type) {
+ TYPE_ICON -> "ICON"
+ TYPE_MOBILE_NEW -> "MOBILE_NEW"
+ TYPE_WIFI_NEW -> "WIFI_NEW"
+ else -> "UNKNOWN"
+ }
+ }
+
+ @JvmStatic
+ fun fromIcon(icon: StatusBarIcon?): StatusBarIconHolder {
+ val wrapper = StatusBarIconHolder()
+ wrapper.icon = icon
+ return wrapper
+ }
+
+ /** Creates a new holder with for the new wifi icon. */
+ @JvmStatic
+ fun forNewWifiIcon(): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ holder.type = TYPE_WIFI_NEW
+ return holder
+ }
+
+ /**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ @JvmStatic
+ fun fromSubIdForModernMobileIcon(subId: Int): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ holder.type = TYPE_MOBILE_NEW
+ holder.tag = subId
+ return holder
+ }
+
+ /** Creates a new StatusBarIconHolder from a CallIndicatorIconState. */
+ @JvmStatic
+ fun fromCallIndicatorState(
+ context: Context,
+ state: CallIndicatorIconState,
+ ): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ val resId = if (state.isNoCalling) state.noCallingResId else state.callStrengthResId
+ val contentDescription =
+ if (state.isNoCalling) state.noCallingDescription else state.callStrengthDescription
+ holder.icon =
+ StatusBarIcon(
+ UserHandle.SYSTEM,
+ context.packageName,
+ Icon.createWithResource(context, resId),
+ 0,
+ 0,
+ contentDescription,
+ )
+ holder.tag = state.subId
+ return holder
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 942d186..b048da492 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.phone.dagger;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
+import com.android.systemui.statusbar.phone.ConfigurationControllerStartable;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* Dagger Module providing {@link CentralSurfacesImpl}.
@@ -34,4 +38,12 @@
@Binds
@SysUISingleton
CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl);
+
+ /**
+ * Starts {@link ConfigurationControllerStartable}
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(ConfigurationControllerStartable.class)
+ CoreStartable bindConfigControllerStartable(ConfigurationControllerStartable impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
new file mode 100644
index 0000000..6cd9993
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.toast
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ToastModule {
+ /** Starts ToastUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(ToastUI::class)
+ fun bindToastUIStartable(service: ToastUI): CoreStartable
+
+ /** Listen to config changes for ToastUI. */
+ @Binds @IntoSet fun bindToastUIConfigChanges(service: ToastUI): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 27f8121..85a455d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -42,6 +42,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.util.Objects;
@@ -51,7 +52,10 @@
* Controls display of text toasts.
*/
@SysUISingleton
-public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
+public class ToastUI implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
// values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
@@ -187,7 +191,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (newConfig.orientation != mOrientation) {
mOrientation = newConfig.orientation;
if (mToast != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 3451ae0..dc2b80c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -22,16 +22,17 @@
import android.util.Log;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
import javax.inject.Inject;
@SysUISingleton
-public class VolumeUI implements CoreStartable {
+public class VolumeUI implements CoreStartable, ConfigurationController.ConfigurationListener {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -60,7 +61,7 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (!mEnabled) return;
mVolumeComponent.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 53217d4..8d06a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,6 +21,7 @@
import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
@@ -36,15 +37,30 @@
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
+import com.android.systemui.volume.VolumeUI;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/** Dagger Module for code in the volume package. */
@Module
public interface VolumeModule {
+ /** Starts VolumeUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumeUI.class)
+ CoreStartable bindVolumeUIStartable(VolumeUI impl);
+
+ /** Listen to config changes for VolumeUI. */
+ @Binds
+ @IntoSet
+ ConfigurationController.ConfigurationListener bindVolumeUIConfigChanges(VolumeUI impl);
+
/** */
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 639276e..b3eab8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -248,8 +248,8 @@
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigChanged(Configuration newConfig) {
+ super.onConfigChanged(newConfig);
mExecutor.runAllReady();
}
@@ -892,7 +892,7 @@
// Switch to long edge cutout(left).
mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
}
@@ -913,7 +913,7 @@
// Switch to long edge cutout(left).
mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
verify(mDotViewController, times(2)).setShowingListener(null);
@@ -949,7 +949,7 @@
// top cutout
mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
// Only top windows should be added.
verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
@@ -976,7 +976,7 @@
// top cutout
mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
// Both top and bottom windows should be added with VISIBLE because of privacy dot and
// cutout, but rounded corners visibility shall be gone because of no rounding.
@@ -1013,7 +1013,7 @@
doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
mDisplayInfo.rotation = Surface.ROTATION_270;
- mScreenDecorations.onConfigurationChanged(null);
+ mScreenDecorations.onConfigChanged(null);
assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
@@ -1145,7 +1145,7 @@
assertThat(mScreenDecorations.mIsRegistered, is(false));
doReturn(true).when(mScreenDecorations).hasOverlays();
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(true));
}
@@ -1156,7 +1156,7 @@
mScreenDecorations.start();
assertThat(mScreenDecorations.mIsRegistered, is(true));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(true));
}
@@ -1168,7 +1168,7 @@
assertThat(mScreenDecorations.mIsRegistered, is(true));
doReturn(false).when(mScreenDecorations).hasOverlays();
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(false));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e3..9bcab57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
- private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private DismissView mDismissView;
@Rule
@@ -65,19 +65,20 @@
stubMenuViewAppearance);
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
}
@Test
public void showDismissView_success() {
- mDismissAnimationController.showDismissView(true);
+ mDragToInteractAnimationController.showDismissView(true);
verify(mDismissView).show();
}
@Test
public void hideDismissView_success() {
- mDismissAnimationController.showDismissView(false);
+ mDragToInteractAnimationController.showDismissView(false);
verify(mDismissView).hide();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 7f12c05..9c8de30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -62,7 +62,7 @@
@Mock
private SecureSettings mSecureSettings;
@Mock
- private DismissAnimationController.DismissCallback mStubDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
private RecyclerView mStubListView;
private MenuView mMenuView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 9797f2a..e1522f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -68,7 +68,7 @@
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
- private DismissAnimationController mDismissAnimationController;
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
@@ -92,10 +92,10 @@
mStubMenuView, stubMenuViewAppearance));
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController =
- spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mDragToInteractAnimationController =
+ spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index b4ae00d..42d2c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -217,6 +217,7 @@
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
@@ -260,14 +261,14 @@
SideFpsOverlayViewBinder(
testScope.backgroundScope,
mContext,
- biometricStatusInteractor,
- displayStateInteractor,
- deviceEntrySideFpsOverlayInteractor,
- fpsUnlockTracker,
- layoutInflater,
- sideFpsProgressBarViewModel,
- sfpsSensorInteractor,
- windowManager
+ { biometricStatusInteractor },
+ { displayStateInteractor },
+ { deviceEntrySideFpsOverlayInteractor },
+ { fpsUnlockTracker },
+ { layoutInflater },
+ { sideFpsProgressBarViewModel },
+ { sfpsSensorInteractor },
+ { windowManager }
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2267bdc..983e4b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -220,6 +220,7 @@
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd..c22d35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@
assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
}
+
+ @Test
+ fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStartTrackingTouch(seekBar)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+ }
+
+ @Test
+ fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e49..db04962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@
verifyNoMoreInteractions(sliderStateListener)
}
+ @Test
+ fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ initTracker(testScheduler)
+
+ // GIVEN a progress due to an external source that lands at the middle of the slider
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+ verify(sliderStateListener).onSelectAndArrow(progress)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // GIVEN a progress due to an external source that lands at the upper bookend
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving back to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // WHEN a progress is recorded due to an external source that lands at the lower bookend
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves back to IDLE and there are no haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider gets an external progress change
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves to IDLE and no haptics are played
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves to WAIT and the wait job starts.
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider changes progress programmatically at the middle
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker stays in the same state and haptics are delivered appropriately
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
private fun initTracker(
scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 0616a34..027dfa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -109,6 +109,7 @@
)
underTest =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
FakeDeviceEntryFingerprintAuthRepository(),
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index c108a80..273ce85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -28,8 +28,8 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -82,7 +82,7 @@
TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_title_text_view);
- controller.onConfigurationChanged(new Configuration());
+ controller.onConfigChanged(new Configuration());
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_title_text_view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 88c728f..b94e483 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -36,8 +36,12 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.flow.timeout
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -59,7 +63,6 @@
@Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
@Mock private lateinit var brightnessController: BrightnessController
@Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
- @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor>
@Mock private lateinit var shadeInteractor: ShadeInteractor
private val clock = FakeSystemClock()
@@ -89,7 +92,6 @@
.thenReturn(brightnessSliderController)
`when`(brightnessSliderController.rootView).thenReturn(View(context))
`when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
- whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor)
whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
}
@@ -180,6 +182,22 @@
assertThat(activityRule.activity.isFinishing()).isFalse()
}
+ @OptIn(FlowPreview::class)
+ @Test
+ fun testFinishOnQSExpanded() = runTest {
+ val isQSExpanded = MutableStateFlow(false)
+ `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded)
+ activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ isQSExpanded.value = true
+ // Observe the activity's state until is it finishing or the timeout is reached, whatever
+ // comes first. This fixes the flakiness seen when using advanceUntilIdle().
+ activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {}
+ assertThat(activityRule.activity.isFinishing()).isTrue()
+ }
+
class TestDialog(
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
brightnessControllerFactory: BrightnessController.Factory,
@@ -194,14 +212,14 @@
accessibilityMgr,
shadeInteractor
) {
- private var finishing = false
+ var finishing = MutableStateFlow(false)
override fun isFinishing(): Boolean {
- return finishing
+ return finishing.value
}
override fun requestFinish() {
- finishing = true
+ finishing.value = true
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 80f8cf1..50349be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -58,13 +58,13 @@
testScope.runTest {
val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 1,
)
assertThat(showNotifs).isEqualTo(true)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 0,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 6eabf44..5e57c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -17,13 +17,17 @@
package com.android.systemui.shared.plugins;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
@@ -40,7 +44,11 @@
import java.lang.ref.WeakReference;
import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -104,6 +112,7 @@
mPluginInstance = mPluginInstanceFactory.create(
mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
TestPlugin.class, mPluginListener);
+ mPluginInstance.setIsDebug(true);
mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
}
@@ -158,7 +167,7 @@
@Test
public void testOnAttach_SkipLoad() {
- mPluginListener.mAttachReturn = false;
+ mPluginListener.mOnAttach = () -> false;
mPluginInstance.onCreate();
assertEquals(1, mPluginListener.mAttachedCount);
assertEquals(0, mPluginListener.mLoadCount);
@@ -166,6 +175,65 @@
assertInstances(0, 0);
}
+ @Test
+ public void testLoadUnloadSimultaneous_HoldsUnload() throws Exception {
+ final Semaphore loadLock = new Semaphore(1);
+ final Semaphore unloadLock = new Semaphore(1);
+
+ mPluginListener.mOnAttach = () -> false;
+ mPluginListener.mOnLoad = () -> {
+ assertNotNull(mPluginInstance.getPlugin());
+
+ // Allow the bg thread the opportunity to delete the plugin
+ loadLock.release();
+ Thread.yield();
+ boolean isLocked = getLock(unloadLock, 1000);
+
+ // Ensure the bg thread failed to do delete the plugin
+ assertNotNull(mPluginInstance.getPlugin());
+ // We expect that bgThread deadlocked holding the semaphore
+ assertFalse(isLocked);
+ };
+
+ AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
+ Thread bgThread = new Thread(() -> {
+ assertTrue(getLock(unloadLock, 10));
+ assertTrue(getLock(loadLock, 3000)); // Wait for the foreground thread
+ assertNotNull(mPluginInstance.getPlugin());
+ // Attempt to delete the plugin, this should block until the load completes
+ mPluginInstance.unloadPlugin();
+ assertNull(mPluginInstance.getPlugin());
+ unloadLock.release();
+ loadLock.release();
+ });
+
+ // This protects the test suite from crashing due to the uncaught exception.
+ bgThread.setUncaughtExceptionHandler((Thread t, Throwable ex) -> {
+ Log.e("testLoadUnloadSimultaneous_HoldsUnload", "Exception from BG Thread", ex);
+ isBgThreadFailed.set(true);
+ });
+
+ loadLock.acquire();
+ mPluginInstance.onCreate();
+
+ assertNull(mPluginInstance.getPlugin());
+ bgThread.start();
+ mPluginInstance.loadPlugin();
+
+ bgThread.join(5000);
+ assertFalse(isBgThreadFailed.get());
+ assertNull(mPluginInstance.getPlugin());
+ }
+
+ private boolean getLock(Semaphore lock, long millis) {
+ try {
+ return lock.tryAcquire(millis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ex) {
+ fail();
+ return false;
+ }
+ }
+
// This target class doesn't matter, it just needs to have a Requires to hit the flow where
// the mock version info is called.
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -226,7 +294,10 @@
}
public class FakeListener implements PluginListener<TestPlugin> {
- public boolean mAttachReturn = true;
+ public Supplier<Boolean> mOnAttach = null;
+ public Runnable mOnDetach = null;
+ public Runnable mOnLoad = null;
+ public Runnable mOnUnload = null;
public int mAttachedCount = 0;
public int mDetachedCount = 0;
public int mLoadCount = 0;
@@ -236,13 +307,16 @@
public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
mAttachedCount++;
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
- return mAttachReturn;
+ return mOnAttach != null ? mOnAttach.get() : true;
}
@Override
public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
mDetachedCount++;
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ if (mOnDetach != null) {
+ mOnDetach.run();
+ }
}
@Override
@@ -261,6 +335,9 @@
assertEquals(expectedContext, pluginContext);
}
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ if (mOnLoad != null) {
+ mOnLoad.run();
+ }
}
@Override
@@ -274,6 +351,9 @@
assertEquals(expectedPlugin, plugin);
}
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ if (mOnUnload != null) {
+ mOnUnload.run();
+ }
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7558974..1236fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -38,6 +38,7 @@
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.metrics.LogMaker;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -84,6 +85,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,6 +220,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
initController(/* viewIsAttached= */ true);
@@ -238,6 +241,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -258,6 +262,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -285,6 +290,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -306,6 +312,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -327,6 +334,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -348,6 +356,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -504,6 +513,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
initController(/* viewIsAttached= */ true);
mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
@@ -545,6 +555,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
// GIVEN: Controller is attached, active notifications is empty,
// and mNotificationStackScrollLayout.onKeyguard() is true
@@ -561,6 +572,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is not empty,
// and mNotificationStackScrollLayout.onKeyguard() is true
@@ -584,6 +596,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is not empty,
// and mNotificationStackScrollLayout.onKeyguard() is false
@@ -607,6 +620,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is empty,
// and mNotificationStackScrollLayout.onKeyguard() is false
@@ -623,6 +637,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
initController(/* viewIsAttached= */ true);
mController.onKeyguardTransitionChanged(
@@ -633,6 +648,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
initController(/* viewIsAttached= */ true);
mController.onKeyguardTransitionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ad7dee3..83ba684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -51,6 +51,8 @@
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -81,6 +83,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -191,7 +194,7 @@
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
- mStackScroller.setResetUserExpandedStatesRunnable(()->{});
+ mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -309,7 +312,9 @@
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ true,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -319,7 +324,9 @@
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ false,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -328,10 +335,14 @@
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ false,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ true,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -385,8 +396,8 @@
mStackScroller.setExpandedHeight(100f);
}
-
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void manageNotifications_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -399,6 +410,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void clearAll_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -411,6 +423,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testInflateFooterView() {
mStackScroller.inflateFooterView();
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -444,7 +457,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
}
@Test
@@ -459,7 +472,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, false, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
}
@Test
@@ -474,7 +487,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
}
@Test
@@ -490,7 +503,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, true, false);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
}
@Test
@@ -505,7 +518,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
}
@Test
@@ -521,7 +534,7 @@
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, false, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
}
@Test
@@ -529,7 +542,8 @@
mStackScroller.setCurrentUserSetup(true);
// add footer
- mStackScroller.inflateFooterView();
+ FooterView view = mock(FooterView.class);
+ mStackScroller.setFooterView(view);
// add notification
ExpandableNotificationRow row = createClearableRow();
@@ -545,6 +559,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testReInflatesFooterViews() {
when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
clearInvocations(mStackScroller);
@@ -554,6 +569,16 @@
}
@Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testReInflatesEmptyShadeView() {
+ when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
+ clearInvocations(mStackScroller);
+ mStackScroller.reinflateViews();
+ verify(mStackScroller, never()).setFooterView(any());
+ verify(mStackScroller).setEmptyShadeView(any());
+ }
+
+ @Test
public void testSetIsBeingDraggedResetsExposedMenu() {
mStackScroller.setIsBeingDragged(true);
verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
@@ -601,6 +626,8 @@
@Test
public void testClearNotifications_clearAllInProgress() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
ExpandableNotificationRow row = createClearableRow();
when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
doReturn(true).when(mStackScroller).isVisible(row);
@@ -645,6 +672,8 @@
@Test
public void testAddNotificationUpdatesSpeedBumpIndex() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -661,6 +690,8 @@
@Test
public void testAddAmbientNotificationNoSpeedBumpUpdate() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -677,6 +708,8 @@
@Test
public void testRemoveNotificationUpdatesSpeedBump() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -872,6 +905,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void hasFilteredOutSeenNotifs_updateFooter() {
mStackScroller.setCurrentUserSetup(true);
@@ -887,6 +921,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
mStackScroller.setHasFilteredOutSeenNotifications(true);
mStackScroller.updateEmptyShadeView(true, false);
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce..e52cefb 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,4 +39,7 @@
sdk_version: "current",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 7744fca..491ed22 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -55,6 +55,7 @@
android.os.Parcel
android.os.Parcelable
android.os.Process
+android.os.ServiceSpecificException
android.os.SystemClock
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index acb5911..d08a97e 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -19,7 +19,13 @@
defaults: ["platform_service_defaults"],
srcs: [":services.backup-sources"],
libs: ["services.core"],
- static_libs: ["app-compat-annotations", "backup_flags_lib"],
+ static_libs: [
+ "app-compat-annotations",
+ "backup_flags_lib",
+ ],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
aconfig_declarations {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 550e17b..2bfdd0a 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -31,4 +31,7 @@
"virtualdevice_flags_lib",
"virtual_camera_service_aidl-java",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5111b08..dd001ec 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -212,6 +212,9 @@
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_genrule {
@@ -230,6 +233,9 @@
java_library {
name: "services.core",
static_libs: ["services.core.priorityboosted"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_host {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efb..08093c0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@
*/
@NonNull
public abstract PackageArchiver getPackageArchiver();
+
+ /**
+ * Returns true if the device is upgrading from an SDK version lower than the one specified.
+ */
+ public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d461643..96b1650 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@
@Retention(RetentionPolicy.SOURCE)
@interface FgsStopReason {}
+ /**
+ * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+ * except:
+ * <ul>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+ * </ul>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@
}
}
+ private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+ r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+ : REASON_DENIED;
+ if (Flags.fgsBootCompleted()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+ && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+ // Filter through types
+ return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+ }
+ // Not BOOT_COMPLETED
+ return true;
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
int callingUid, int callingPid, String callingProcessName,
int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@
// anyway, so we just remove the SHORT_SERVICE type.
foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
+ if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+ throw new ForegroundServiceStartNotAllowedException("FGS type "
+ + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+ + " not allowed to start from BOOT_COMPLETED!");
+ }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1d69905..72e62c3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
@@ -73,6 +79,9 @@
= "fgservice_screen_on_before_time";
private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
= "fgservice_screen_on_after_time";
+
+ private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+ private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+ | FOREGROUND_SERVICE_TYPE_HEALTH
+ | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+ | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ | FOREGROUND_SERVICE_TYPE_LOCATION;
+
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -446,6 +464,9 @@
// on until we will stop reporting it.
public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
+ // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+ public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
// to avoid thrashing of provider processes under low memory situations.
@@ -629,6 +650,10 @@
// foreground service background start restriction.
volatile boolean mFgsStartRestrictionNotificationEnabled = false;
+ // Indicates whether PSS profiling in AppProfiler is force-enabled, even if RSS is used by
+ // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
+ volatile boolean mForceEnablePssProfiling = false;
+
/**
* Indicates whether the foreground service background start restriction is enabled for
* caller app that is targeting S+.
@@ -958,6 +983,9 @@
private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI =
Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS);
+ private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
+ Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
+
/**
* The threshold to decide if a given association should be dumped into metrics.
*/
@@ -1368,6 +1396,7 @@
mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI,
false, this);
}
+ mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
updateConstants();
if (mSystemServerAutomaticHeapDumpEnabled) {
updateEnableAutomaticSystemServerHeapDumps();
@@ -1383,6 +1412,7 @@
// The following read from Settings.
updateActivityStartsLoggingEnabled();
updateForegroundServiceStartsLoggingEnabled();
+ updateForceEnablePssProfiling();
// Read DropboxRateLimiter params from flags.
mService.initDropboxRateLimiter();
}
@@ -1424,6 +1454,8 @@
updateForegroundServiceStartsLoggingEnabled();
} else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) {
updateEnableAutomaticSystemServerHeapDumps();
+ } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
+ updateForceEnablePssProfiling();
}
}
@@ -1450,6 +1482,8 @@
DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+ FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+ DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -1536,6 +1570,11 @@
Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, 1) == 1;
}
+ private void updateForceEnablePssProfiling() {
+ mForceEnablePssProfiling = Settings.Global.getInt(mResolver,
+ Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
+ }
+
private void updateBackgroundActivityStarts() {
mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2091,6 +2130,8 @@
pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+ pw.print(" "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+ pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f8451fd..671c8e9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -327,7 +327,6 @@
import android.os.DropBoxManager;
import android.os.FactoryTest;
import android.os.FileUtils;
-import android.os.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.IDeviceIdentifiersPolicyService;
@@ -8608,7 +8607,7 @@
final long initialIdlePssOrRss, lastPssOrRss, lastSwapPss;
synchronized (mAppProfiler.mProfilerLock) {
initialIdlePssOrRss = pr.getInitialIdlePssOrRss();
- lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+ lastPssOrRss = mAppProfiler.isProfilingPss()
? pr.getLastPss() : pr.getLastRss();
lastSwapPss = pr.getLastSwapPss();
}
@@ -8618,14 +8617,14 @@
final StringBuilder sb2 = new StringBuilder(128);
sb2.append("Kill");
sb2.append(proc.processName);
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mAppProfiler.isProfilingPss()) {
sb2.append(" in idle maint: pss=");
} else {
sb2.append(" in idle maint: rss=");
}
sb2.append(lastPssOrRss);
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mAppProfiler.isProfilingPss()) {
sb2.append(", swapPss=");
sb2.append(lastSwapPss);
sb2.append(", initialPss=");
@@ -8640,7 +8639,7 @@
Slog.wtfQuiet(TAG, sb2.toString());
mHandler.post(() -> {
synchronized (ActivityManagerService.this) {
- proc.killLocked(!Flags.removeAppProfilerPssCollection()
+ proc.killLocked(mAppProfiler.isProfilingPss()
? "idle maint (pss " : "idle maint (rss " + lastPssOrRss
+ " from " + initialIdlePssOrRss + ")",
ApplicationExitInfo.REASON_OTHER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 00dd169..848a2b0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@
} else if (opt.equals("--dismiss-keyguard-if-insecure")
|| opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
+ } else if (opt.equals("--allow-fgs-start-reason")) {
+ final int reasonCode = Integer.parseInt(getNextArgRequired());
+ mBroadcastOptions = BroadcastOptions.makeBasic();
+ mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ reasonCode,
+ "");
} else {
return false;
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 2e0aec9..e4956b3 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -602,7 +602,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case COLLECT_PSS_BG_MSG:
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (isProfilingPss()) {
collectPssInBackground();
} else {
collectRssInBackground();
@@ -748,6 +748,11 @@
} while (true);
}
+ boolean isProfilingPss() {
+ return !Flags.removeAppProfilerPssCollection()
+ || mService.mConstants.mForceEnablePssProfiling;
+ }
+
// This method is analogous to collectPssInBackground() and is intended to be used as a
// replacement if Flags.removeAppProfilerPssCollection() is enabled. References to PSS in
// methods outside of AppProfiler have generally been kept where a new RSS equivalent is not
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b507a60..f49e25a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -143,7 +143,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.net.NetworkPolicyManager;
-import android.os.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
@@ -2418,7 +2417,7 @@
// normally be a B service, but if we are low on RAM and it
// is large we want to force it down since we would prefer to
// keep launcher over it.
- long lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+ long lastPssOrRss = mService.mAppProfiler.isProfilingPss()
? app.mProfile.getLastPss() : app.mProfile.getLastRss();
// RSS is larger than PSS, but the RSS/PSS ratio varies per-process based on how
@@ -2427,9 +2426,8 @@
//
// TODO(b/296454553): Tune the second value so that the relative number of
// service B is similar before/after this flag is enabled.
- double thresholdModifier = !Flags.removeAppProfilerPssCollection()
- ? 1
- : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
+ double thresholdModifier = mService.mAppProfiler.isProfilingPss()
+ ? 1 : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
double cachedRestoreThreshold =
mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e57206e..b03183c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -94,7 +94,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.DropBoxManager;
-import android.os.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -2482,7 +2481,6 @@
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
app.getDisabledCompatChanges(),
- bindOverrideSysprops,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else if (hostingRecord.usesAppZygote()) {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -4733,7 +4731,7 @@
pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
// These values won't be collected if the flag is enabled.
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (service.mAppProfiler.isProfilingPss()) {
pw.print(" lastPss=");
DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
pw.print(" lastSwapPss=");
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 8ca64f8..d8f797c 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,7 +23,6 @@
import android.app.ProcessMemoryState.HostingComponentType;
import android.content.pm.ApplicationInfo;
import android.os.Debug;
-import android.os.Flags;
import android.os.Process;
import android.os.SystemClock;
import android.util.DebugUtils;
@@ -677,7 +676,7 @@
void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
synchronized (mProfilerLock) {
// TODO(b/297542292): Remove this case once PSS profiling is replaced
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mService.mAppProfiler.isProfilingPss()) {
pw.print(prefix);
pw.print("lastPssTime=");
TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 5ad921f..3391ec7 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -29,7 +29,6 @@
import android.annotation.ElapsedRealtimeLong;
import android.app.ActivityManager;
import android.content.ComponentName;
-import android.os.Flags;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Slog;
@@ -1351,7 +1350,7 @@
}
if (mNotCachedSinceIdle) {
pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mService.mAppProfiler.isProfilingPss()) {
pw.print(" initialIdlePss=");
} else {
pw.print(" initialIdleRss=");
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index cc56110..d0d647c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -179,6 +179,7 @@
"text",
"threadnetwork",
"tv_system_ui",
+ "usb",
"vibrator",
"virtual_devices",
"wallet_integration",
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 2cceb5a..cbcd8f5 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -346,6 +346,7 @@
if (apc.getPlayerProxy() != null) {
applyVolumeShaperInternal(apc, piid, volShaper,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ mFadedPlayers.put(piid, volShaper);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading out player piid:" + piid
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6af223b..0f964bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -78,6 +78,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -99,6 +100,9 @@
mBiometricStateCallback;
@NonNull
private final FaceProviderFunction mFaceProviderFunction;
+ @NonNull private final Function<String, FaceProvider> mFaceProvider;
+ @NonNull
+ private final Supplier<String[]> mAidlInstanceNameSupplier;
interface FaceProviderFunction {
FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
@@ -671,23 +675,9 @@
final List<ServiceProvider> providers = new ArrayList<>();
for (String instance : instances) {
- final String fqName = IFace.DESCRIPTOR + "/" + instance;
- final IFace face = IFace.Stub.asInterface(
- Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
- if (face == null) {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
- continue;
- }
- try {
- final SensorProps[] props = face.getSensorProps();
- final FaceProvider provider = new FaceProvider(getContext(),
- mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
- false /* resetLockoutRequiresChallenge */);
- providers.add(provider);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
- }
+ final FaceProvider provider = mFaceProvider.apply(instance);
+ Slog.i(TAG, "Adding AIDL provider: " + instance);
+ providers.add(provider);
}
return providers;
@@ -700,7 +690,7 @@
mRegistry.registerAll(() -> {
List<String> aidlSensors = new ArrayList<>();
- final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ final String[] instances = mAidlInstanceNameSupplier.get();
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
@@ -813,11 +803,15 @@
public FaceService(Context context) {
this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
+ () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
}
- @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
- Supplier<IBiometricService> biometricServiceSupplier) {
+ @VisibleForTesting FaceService(Context context,
+ FaceProviderFunction faceProviderFunction,
+ Supplier<IBiometricService> biometricServiceSupplier,
+ Function<String, FaceProvider> faceProvider,
+ Supplier<String[]> aidlInstanceNameSupplier) {
super(context);
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
@@ -830,6 +824,28 @@
mBiometricStateCallback.start(mRegistry.getProviders());
}
});
+ mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
+
+ mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
+ final String fqName = IFace.DESCRIPTOR + "/" + name;
+ final IFace face = IFace.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ if (face == null) {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ return null;
+ }
+ try {
+ final SensorProps[] props = face.getSensorProps();
+ return new FaceProvider(getContext(),
+ mBiometricStateCallback, props, name, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ false /* resetLockoutRequiresChallenge */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+ }
+
+ return null;
+ };
if (Flags.deHidl()) {
mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3024dd2..8910b6e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,6 +82,7 @@
public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+ public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
// How long the current sensor reading is assumed to be valid beyond the current time.
// This provides a bit of prediction, as well as ensures that the weight for the last sample is
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08f..bc3f9dd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@
if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final DisplayPowerControllerInterface displayPowerController =
mDisplayPowerControllers.get(id);
- ready &= displayPowerController.requestPowerState(request,
- waitForNegativeProximity);
+ if (displayPowerController != null) {
+ ready &= displayPowerController.requestPowerState(request,
+ waitForNegativeProximity);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9..a43f93a 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -65,6 +67,22 @@
return true;
}
+ @Override
+ public float[] getAutoBrightnessLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLevels(mode);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 06e5f99..734381b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2214,6 +2214,20 @@
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 519224a..7df6114 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1886,6 +1886,24 @@
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c279184..13acb3f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@
* Indicate that boot has been completed and the screen is ready to update.
*/
void onBootCompleted();
+
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 200d88a..01a8d360a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -104,8 +104,7 @@
public void resetHdrConfig(HdrBrightnessData data, int width, int height,
float minimumHdrPercentOfScreen, IBinder displayToken) {
mHdrBrightnessData = data;
- mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
- : (float) (width * height) * minimumHdrPercentOfScreen;
+ mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
mHdrListener.unregister(mRegisteredDisplayToken);
@@ -115,7 +114,7 @@
// new token not null and hdr min % of the screen is set, subscribe.
// e.g. for virtual display, HBM data will be missing and HdrListener
// should not be registered
- if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
+ if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
mHdrListener.register(displayToken);
mRegisteredDisplayToken = displayToken;
}
@@ -140,8 +139,11 @@
pw.println(" mDesiredMaxBrightness=" + mDesiredMaxBrightness);
pw.println(" mTransitionRate=" + mTransitionRate);
pw.println(" mDesiredTransitionRate=" + mDesiredTransitionRate);
+ pw.println(" mHdrVisible=" + mHdrVisible);
+ pw.println(" mHdrListener.mHdrMinPixels=" + mHdrListener.mHdrMinPixels);
pw.println(" mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null"
: mHdrBrightnessData.toString()));
+ pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
pw.println(" mAmbientLux=" + mAmbientLux);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 24e23003..087c525 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2523,9 +2523,9 @@
// Native callback.
@SuppressWarnings("unused")
private int interceptMotionBeforeQueueingNonInteractive(int displayId,
- long whenNanos, int policyFlags) {
+ int source, int action, long whenNanos, int policyFlags) {
return mWindowManagerCallbacks.interceptMotionBeforeQueueingNonInteractive(
- displayId, whenNanos, policyFlags);
+ displayId, source, action, whenNanos, policyFlags);
}
// Native callback.
@@ -2901,8 +2901,8 @@
* processing when the device is in a non-interactive state since these events are normally
* dropped.
*/
- int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags);
+ int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags);
/**
* This callback is invoked just before the key is about to be sent to an application.
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5..9c4225d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@
location.setExtras(mLocationExtras.getBundle());
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
if (mStarted) {
mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@
}
}
- reportLocation(LocationResult.wrap(locations).validate());
+ try {
+ reportLocation(LocationResult.wrap(locations).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
Runnable[] listeners;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 91e6a80..7d44aec 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.altitude.AltitudeConverter;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
if (D) {
Log.v(TAG, mName + " provider registration " + getIdentity()
- + " dropped delivery - too fast");
+ + " dropped delivery - too fast (deltaMs="
+ + deltaMs + ").");
}
return false;
}
@@ -2574,29 +2576,17 @@
@GuardedBy("mMultiplexerLock")
@Nullable
private LocationResult processReportedLocation(LocationResult locationResult) {
- LocationResult processed = locationResult.filter(location -> {
- if (!location.isMock()) {
- if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
- return false;
- }
- }
-
- if (!location.isComplete()) {
- Log.e(TAG, "blocking incomplete location from " + mName + " provider");
- return false;
- }
-
- return true;
- });
- if (processed == null) {
+ try {
+ locationResult.validate();
+ } catch (BadLocationException e) {
+ Log.e(TAG, "Dropping invalid locations: " + e);
return null;
}
// Attempt to add a missing MSL altitude on behalf of the provider.
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
"enable_location_provider_manager_msl", true)) {
- return processed.map(location -> {
+ return locationResult.map(location -> {
if (!location.hasMslAltitude() && location.hasAltitude()) {
try {
Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@
return location;
});
}
- return processed;
+ return locationResult;
}
@GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4..4efacd7 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@
Location location = new Location(l);
location.setIsFromMockProvider(true);
mLocation = location;
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da..a597edd 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@
return;
}
- reportLocation(LocationResult.wrap(location).validate());
+ reportLocation(LocationResult.wrap(location));
}
}
@@ -316,8 +316,7 @@
if (mProxy != this) {
return;
}
-
- reportLocation(LocationResult.wrap(locations).validate());
+ reportLocation(LocationResult.wrap(locations));
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 38f0df4..9088cb9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2700,11 +2700,8 @@
// session info from them.
sessionInfo = mSystemProvider.getDefaultSessionInfo();
}
- // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
- notifySessionCreatedToRouter(
- matchingRequest.mRouterRecord,
- toOriginalRequestId(uniqueRequestId),
- sessionInfo);
+ matchingRequest.mRouterRecord.notifySessionCreated(
+ toOriginalRequestId(uniqueRequestId), sessionInfo);
}
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2812,11 +2809,6 @@
return true;
}
- private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
- int requestId, @NonNull RoutingSessionInfo sessionInfo) {
- routerRecord.notifySessionCreated(requestId, sessionInfo);
- }
-
private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
int requestId) {
try {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c20..07b333a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
@@ -89,6 +90,12 @@
}
@Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ // TODO: Implement when MediaSession2 knows about its owner pid.
+ return null;
+ }
+
+ @Override
public boolean isSystemPriority() {
// System priority session is currently only allowed for telephony, so it's OK to stick to
// the media1 API at this moment.
@@ -217,7 +224,8 @@
synchronized (mLock) {
service = mService;
}
- service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ service.onSessionPlaybackStateChanged(
+ MediaSession2Record.this, playbackActive, /* playbackState= */ null);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca..cce66e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
private final Object mLock = new Object();
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+ mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
+ private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+ return new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mOwnerPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mOwnerPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
+ }
+
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -681,6 +706,11 @@
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
+ @Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ return mForegroundServiceDelegationOptions;
+ }
+
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
- MediaSessionRecord.this, shouldUpdatePriority);
+ MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02..99c8ea9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.AudioManager;
+import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -51,6 +53,15 @@
int getUserId();
/**
+ * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+ * service with changes in the {@link PlaybackState} for this session.
+ *
+ * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+ * manager service with changes in the {@link PlaybackState} for this session.
+ */
+ ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+ /**
* Check if this session has system priority and should receive media buttons before any other
* sessions.
*
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c59511..db39b5e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,6 +61,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -144,6 +147,7 @@
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
private ActivityManagerLocal mActivityManagerLocal;
+ private ActivityManagerInternal mActivityManagerInternal;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
@Override
@@ -285,7 +290,8 @@
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
-
+ setForegroundServiceAllowance(
+ record, /* allowRunningInForeground= */ record.isActive());
mHandler.postSessionsChanged(record);
}
}
@@ -371,8 +377,10 @@
}
}
- void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
- boolean shouldUpdatePriority) {
+ void onSessionPlaybackStateChanged(
+ MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority,
+ @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +388,10 @@
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+ if (playbackState != null) {
+ setForegroundServiceAllowance(
+ record, playbackState.shouldAllowServiceToRunInForeground());
+ }
}
}
@@ -543,9 +555,30 @@
}
session.close();
+ setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
mHandler.postSessionsChanged(session);
}
+ private void setForegroundServiceAllowance(
+ MediaSessionRecordImpl record, boolean allowRunningInForeground) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (allowRunningInForeground) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ }
+ }
+
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46e7041..e4e48bd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4090,6 +4090,7 @@
}
fout.decreaseIndent();
+ fout.println();
fout.println("Admin restricted uids for metered data:");
fout.increaseIndent();
size = mMeteredRestrictedUids.size();
@@ -4099,6 +4100,7 @@
}
fout.decreaseIndent();
+ fout.println();
fout.println("Network to interfaces:");
fout.increaseIndent();
for (int i = 0; i < mNetworkToIfaces.size(); ++i) {
@@ -4108,6 +4110,10 @@
fout.decreaseIndent();
fout.println();
+ fout.print("Active notifications: ");
+ fout.println(mActiveNotifs);
+
+ fout.println();
mStatLogger.dump(fout);
mLogger.dumpLogs(fout);
@@ -6672,7 +6678,7 @@
* Build unique tag that identifies an active {@link NetworkPolicy}
* notification of a specific type, like {@link #TYPE_LIMIT}.
*/
- private String buildNotificationTag(NetworkPolicy policy, int type) {
+ private static String buildNotificationTag(NetworkPolicy policy, int type) {
return TAG + ":" + policy.template.hashCode() + ":" + type;
}
@@ -6683,5 +6689,10 @@
public int getId() {
return mId;
}
+
+ @Override
+ public String toString() {
+ return mTag;
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 13bf336c..ff415c1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5472,20 +5472,13 @@
}
@Override
- public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
+ public void setAutomaticZenRuleState(String id, Condition condition) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
-
- if (android.app.Flags.modesApi()) {
- if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
- throw new IllegalArgumentException(String.format(
- "Mismatch between fromUser (%s) and condition.source (%s)",
- fromUser, Condition.sourceToString(condition.source)));
- }
- }
+ boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
Binder.getCallingUid());
@@ -5508,7 +5501,7 @@
if (android.app.Flags.modesApi()
&& fromUser
&& !isCallerSystemOrSystemUiOrShell()) {
- throw new SecurityException(String.format(
+ throw new SecurityException(TextUtils.formatSimple(
"Calling %s with fromUser == true is only allowed for system", method));
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1660c3e..e546f42 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -21,13 +21,11 @@
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
@@ -39,6 +37,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -96,6 +95,7 @@
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
private final Object mLock = new Object();
+ private final Injector mInjector;
private final Context mContext;
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
@@ -346,6 +346,14 @@
AtomicFile getMappingFile() {
return mMappingFile;
}
+
+ UserManager getUserManager() {
+ return mContext.getSystemService(UserManager.class);
+ }
+
+ DevicePolicyManager getDevicePolicyManager() {
+ return mContext.getSystemService(DevicePolicyManager.class);
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -357,6 +365,7 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
BugreportManagerServiceImpl(Injector injector) {
+ mInjector = injector;
mContext = injector.getContext();
mAppOps = mContext.getSystemService(AppOpsManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -389,12 +398,7 @@
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, bugreportMode
== BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
- final long identity = Binder.clearCallingIdentity();
- try {
- ensureUserCanTakeBugReport(bugreportMode);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ ensureUserCanTakeBugReport(bugreportMode);
Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
synchronized (mLock) {
@@ -433,7 +437,6 @@
@RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
FileDescriptor bugreportFd, String bugreportFile,
-
boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
@@ -565,54 +568,48 @@
}
/**
- * Validates that the current user is an admin user or, when bugreport is requested remotely
- * that the current user is an affiliated user.
+ * Validates that the calling user is an admin user or, when bugreport is requested remotely
+ * that the user is an affiliated user.
*
- * @throws IllegalArgumentException if the current user is not an admin user
+ * @throws IllegalArgumentException if the calling user is not an admin user
*/
private void ensureUserCanTakeBugReport(int bugreportMode) {
- UserInfo currentUser = null;
+ // Get the calling userId before clearing the caller identity.
+ int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
+ boolean isAdminUser = false;
+ final long identity = Binder.clearCallingIdentity();
try {
- currentUser = ActivityManager.getService().getCurrentUser();
- } catch (RemoteException e) {
- // Impossible to get RemoteException for an in-process call.
+ isAdminUser = mInjector.getUserManager().isUserAdmin(callingUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
-
- if (currentUser == null) {
- logAndThrow("There is no current user, so no bugreport can be requested.");
- }
-
- if (!currentUser.isAdmin()) {
+ if (!isAdminUser) {
if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
- && isCurrentUserAffiliated(currentUser.id)) {
+ && isUserAffiliated(callingUserId)) {
return;
}
- logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
- + " Only admin users are allowed to take bugreport.", currentUser.id));
+ logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
+ + " Only admin users are allowed to take bugreport.", callingUserId));
}
}
/**
- * Returns {@code true} if the device has device owner and the current user is affiliated
+ * Returns {@code true} if the device has device owner and the specified user is affiliated
* with the device owner.
*/
- private boolean isCurrentUserAffiliated(int currentUserId) {
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ private boolean isUserAffiliated(int userId) {
+ DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
int deviceOwnerUid = dpm.getDeviceOwnerUserId();
if (deviceOwnerUid == UserHandle.USER_NULL) {
return false;
}
- int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
-
- Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid
- + " currentUserId: " + currentUserId);
-
- if (callingUserId != deviceOwnerUid) {
- logAndThrow("Caller is not device owner on provisioned device.");
+ if (DEBUG) {
+ Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
}
- if (!dpm.isAffiliatedUser(currentUserId)) {
- logAndThrow("Current user is not affiliated to the device owner.");
+
+ if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
+ logAndThrow("User " + userId + " is not affiliated to the device owner.");
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 7f58e75e..e830d28 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -827,7 +827,8 @@
// action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
// installer app or null for registered apps. The callback only need to send back to the
// registered apps so we check the null condition here.
- notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+ notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList,
+ null /* filterExtras */);
}
}
@@ -975,14 +976,16 @@
final Bundle options = new BroadcastOptions()
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
+ BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver =
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ snapshot, callingUid, intentExtras);
mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
extras, flags, null /* targetPkg */, null /* finishedReceiver */,
new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
- (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
- snapshot, callingUid, intentExtras),
+ filterExtrasForReceiver,
options));
notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver);
}
void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
@@ -1068,9 +1071,10 @@
@Nullable Bundle extras,
@NonNull int[] userIds,
@NonNull int[] instantUserIds,
- @Nullable SparseArray<int[]> broadcastAllowList) {
+ @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtras) {
mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
- instantUserIds, broadcastAllowList, mHandler);
+ instantUserIds, broadcastAllowList, mHandler, filterExtras);
}
private void notifyResourcesChanged(boolean mediaStatus,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b704..c920ca8 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@
int returnCodeOfChild;
for (int childId : childUserIds) {
if (childId == userId) continue;
+ if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+ continue;
+ }
// If package is not present in child then don't attempt to delete.
if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 81d5d81..5225529 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -607,6 +607,8 @@
private final boolean mIsUpgrade;
private final boolean mIsPreNMR1Upgrade;
private final boolean mIsPreQUpgrade;
+ // If mIsUpgrade == true, contains the prior SDK version, else -1.
+ private final int mPriorSdkVersion;
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@@ -1891,6 +1893,7 @@
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
+ mPriorSdkVersion = testParams.priorSdkVersion;
mIsUpgrade = testParams.isUpgrade;
mMetrics = testParams.Metrics;
mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2230,7 +2233,7 @@
"Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+ PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
-
+ mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
mInjector.getSystemPartitions());
@@ -4623,7 +4626,7 @@
});
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras, userIds, null /* instantUserIds */,
- broadcastAllowList, mHandler);
+ broadcastAllowList, mHandler, null /* filterExtras */);
}
}
}
@@ -7073,7 +7076,7 @@
}
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras, userIds, null /* instantUserIds */,
- broadcastAllowList, mHandler);
+ broadcastAllowList, mHandler, null /* filterExtras */);
}
@Override
@@ -7099,6 +7102,12 @@
mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
visibilityAllowList, mHandler);
}
+
+ @Override
+ public boolean isUpgradingFromLowerThan(int sdkVersion) {
+ final boolean isUpgrading = mPriorSdkVersion != -1;
+ return isUpgrading && mPriorSdkVersion < sdkVersion;
+ }
}
private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 86d78dc..2d79718 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -65,6 +65,7 @@
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreQupgrade;
+ public int priorSdkVersion = -1;
public boolean isUpgrade;
public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
public DisplayMetrics Metrics;
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 1bb0730..d05e4c6 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -41,6 +41,7 @@
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
+import java.util.function.BiFunction;
/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
* used by PackageMonitor to improve the broadcast latency. */
@@ -105,8 +106,9 @@
extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
}
extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
- notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
- userIds /* userIds */, instantUserIds, broadcastAllowList, handler);
+ notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras,
+ userIds /* userIds */, instantUserIds, broadcastAllowList, handler,
+ null /* filterExtras */);
}
public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -120,7 +122,8 @@
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, handler);
+ null /* instantUserIds */, null /* broadcastAllowList */, handler,
+ null /* filterExtras */);
}
public void notifyPackageChanged(String packageName, boolean dontKillApp,
@@ -137,12 +140,12 @@
extras.putString(Intent.EXTRA_REASON, reason);
}
notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
- instantUserIds, broadcastAllowList, handler);
+ instantUserIds, broadcastAllowList, handler, null /* filterExtras */);
}
public void notifyPackageMonitor(String action, String pkg, Bundle extras,
int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
- Handler handler) {
+ Handler handler, BiFunction<Integer, Bundle, Bundle> filterExtras) {
if (!isAllowedCallbackAction(action)) {
return;
}
@@ -160,10 +163,11 @@
if (ArrayUtils.isEmpty(instantUserIds)) {
doNotifyCallbacksByAction(
- action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
+ action, pkg, extras, resolvedUserIds, broadcastAllowList, handler,
+ filterExtras);
} else {
doNotifyCallbacksByAction(action, pkg, extras, instantUserIds, broadcastAllowList,
- handler);
+ handler, filterExtras);
}
} catch (RemoteException e) {
// do nothing
@@ -199,11 +203,13 @@
synchronized (mLock) {
callbacks = mCallbacks;
}
- doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler);
+ doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+ null /* filterExtrasFunction */);
}
private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
- SparseArray<int[]> broadcastAllowList, Handler handler) {
+ SparseArray<int[]> broadcastAllowList, Handler handler,
+ BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
RemoteCallbackList<IRemoteCallback> callbacks;
synchronized (mLock) {
callbacks = mCallbacks;
@@ -223,12 +229,13 @@
final int[] allowUids =
broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
- doNotifyCallbacks(callbacks, intent, userId, allowUids, handler);
+ doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
}
}
private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
- Intent intent, int userId, int[] allowUids, Handler handler) {
+ Intent intent, int userId, int[] allowUids, Handler handler,
+ BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
handler.post(() -> callbacks.broadcast((callback, user) -> {
RegisterUser registerUser = (RegisterUser) user;
if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
@@ -236,6 +243,15 @@
return;
}
int registerUid = registerUser.getUid();
+ if (filterExtrasFunction != null) {
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
+ if (filteredExtras != null) {
+ intent.replaceExtras(filteredExtras);
+ }
+ }
+ }
if (allowUids != null && registerUid != Process.SYSTEM_UID
&& !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae273..b286b12 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@
import android.os.Message;
import android.os.PatternMatcher;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SELinux;
import android.os.SystemClock;
import android.os.Trace;
@@ -3189,6 +3190,9 @@
pkg.isScannedAsStoppedSystemApp());
if (!pkg.hasSharedUser()) {
serializer.attributeInt(null, "userId", pkg.getAppId());
+
+ serializer.attributeBoolean(null, "isSdkLibrary",
+ pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
} else {
serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
}
@@ -4039,10 +4043,12 @@
int targetSdkVersion = 0;
byte[] restrictUpdateHash = null;
boolean isScannedAsStoppedSystemApp = false;
+ boolean isSdkLibrary = false;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
appId = parseAppId(parser);
+ isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
sharedUserAppId = parseSharedUserAppId(parser);
codePathStr = parser.getAttributeValue(null, "codePath");
@@ -4157,7 +4163,8 @@
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <package> has no codePath at "
+ parser.getPositionDescription());
- } else if (appId > 0) {
+ } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+ && Flags.disallowSdkLibsToBeApps())) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
appId, pkgFlags, pkgPrivateFlags, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
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 d3931a3..10e6edc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
@@ -1228,6 +1229,11 @@
sPlatformPermissions.put(permission, permissionInfo);
}
} catch (PackageManager.NameNotFoundException ignored) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as package"
+ + " not found when retrieving permission info");
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
}
@@ -1347,17 +1353,34 @@
// way we can avoid the datasource creating an attribution context for every call.
if (!(fromDatasource && current.equals(attributionSource))
&& next != null && !current.isTrusted(context)) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as "
+ + current + " attribution source isn't a data source and "
+ + current + " isn't trusted");
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
// If we already checked the permission for this one, skip the work
if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
permission, current)) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as we"
+ + " aren't skipping permission checks and permission check returns"
+ + " false for " + current);
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
if (next != null && !checkPermission(context, permissionManagerServiceInt,
permission, next)) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " permission check returns false for next source " + next);
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -1402,6 +1425,10 @@
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
+ + " mode is MODE_ERRORED for " + attributionSource);
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
case AppOpsManager.MODE_IGNORED: {
@@ -1670,6 +1697,12 @@
final AttributionSource resolvedAttributionSource = resolveAttributionSource(
context, accessorSource);
if (resolvedAttributionSource.getPackageName() == null) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as resolved"
+ + "package name for " + resolvedAttributionSource + " returned"
+ + " null");
+ }
return AppOpsManager.MODE_ERRORED;
}
int notedOp = op;
@@ -1683,6 +1716,13 @@
if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
if (checkedOpResult == MODE_ERRORED) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ }
return checkedOpResult;
}
notedOp = attributedOp;
@@ -1722,7 +1762,22 @@
throw new SecurityException(msg + ":" + e.getMessage());
}
}
- return Math.max(checkedOpResult, notedOpResult);
+ int result = Math.max(checkedOpResult, notedOpResult);
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+ if (result == checkedOpResult) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ } else {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " noteOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + notedOp
+ + " returned MODE_ERRORED");
+ }
+ }
+ return result;
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3000a1c..5b13d3fe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5201,8 +5201,8 @@
// TODO(b/117479243): handle it in InputPolicy
/** {@inheritDoc} */
@Override
- public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags) {
+ public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 03a7bd3..3016b39 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -706,12 +706,14 @@
* Generally, it's best to keep as little as possible in the queue thread
* because it's the most fragile.
* @param displayId The display ID of the motion event.
+ * @param source the {@link InputDevice} source that caused the motion.
+ * @param action the {@link MotionEvent} action for the motion.
* @param policyFlags The policy flags associated with the motion.
*
* @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
*/
- int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags);
+ int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags);
/**
* Called from the input dispatcher thread before a key is dispatched to a window.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 145eb3b..ca8afe1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -223,7 +223,6 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -949,7 +948,7 @@
private int mConfigurationSeq;
/**
- * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
+ * Temp configs used in {@link #ensureActivityConfiguration()}
*/
private final Configuration mTmpConfig = new Configuration();
private final Rect mTmpBounds = new Rect();
@@ -1511,7 +1510,7 @@
updatePictureInPictureMode(null, false);
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
+ ensureActivityConfiguration();
}
}
}
@@ -1530,8 +1529,7 @@
// precede the configuration change from the resize.
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- true /* ignoreVisibility */);
+ ensureActivityConfiguration(true /* ignoreVisibility */);
if (inPictureInPictureMode && findMainWindow() == null) {
// Prevent malicious app entering PiP without valid WindowState, which can in turn
// result a non-touchable PiP window since the InputConsumer for PiP requires it.
@@ -3107,7 +3105,7 @@
// {@link #returningOptions} of the activity under this one can be applied in
// {@link #handleAlreadyVisible()}.
if (changed || !occludesParent) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
return changed;
}
@@ -3747,8 +3745,8 @@
}
if (ensureVisibility) {
- mDisplayContent.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, true /* notifyClients */);
+ mDisplayContent.ensureActivitiesVisible(null /* starting */,
+ true /* notifyClients */);
}
}
@@ -4165,8 +4163,7 @@
if (rootTask != null && rootTask.shouldSleepOrShutDownActivities()) {
// Activity is always relaunched to either resumed or paused state. If it was
// relaunched while hidden (by keyguard or smth else), it should be stopped.
- rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
}
}
@@ -4681,14 +4678,12 @@
void setShowWhenLocked(boolean showWhenLocked) {
mShowWhenLocked = showWhenLocked;
- mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */);
+ mAtmService.mRootWindowContainer.ensureActivitiesVisible();
}
void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
mInheritShownWhenLocked = inheritShowWhenLocked;
- mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */);
+ mAtmService.mRootWindowContainer.ensureActivitiesVisible();
}
/**
@@ -6413,7 +6408,7 @@
}
mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
/**
@@ -7894,7 +7889,7 @@
void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
Configuration config) {
super.applyFixedRotationTransform(info, displayFrames, config);
- ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ ensureActivityConfiguration();
}
/**
@@ -7989,7 +7984,7 @@
startFreezingScreen(originalDisplayRotation);
// This activity may relaunch or perform configuration change so once it has reported drawn,
// the screen can be unfrozen.
- ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+ ensureActivityConfiguration();
if (mTransitionController.isCollecting(this)) {
// In case the task was changed from PiP but still keeps old transform.
task.resetSurfaceControlTransforms();
@@ -8017,7 +8012,7 @@
// the request is handled at task level with letterbox.
if (!getMergedOverrideConfiguration().equals(
mLastReportedConfiguration.getMergedConfiguration())) {
- ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
+ ensureActivityConfiguration(
false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
if (mTransitionController.inPlayingTransition(this)) {
mTransitionController.mValidateActivityCompat.add(this);
@@ -9525,14 +9520,12 @@
return mLastReportedDisplayId != getDisplayId();
}
- boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
- return ensureActivityConfiguration(globalChanges, preserveWindow,
- false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
+ boolean ensureActivityConfiguration() {
+ return ensureActivityConfiguration(false /* ignoreVisibility */);
}
- boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
- boolean ignoreVisibility) {
- return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility,
+ boolean ensureActivityConfiguration(boolean ignoreVisibility) {
+ return ensureActivityConfiguration(ignoreVisibility,
false /* isRequestedOrientationChanged */);
}
@@ -9540,9 +9533,6 @@
* Make sure the given activity matches the current configuration. Ensures the HistoryRecord
* is updated with the correct configuration and all other bookkeeping is handled.
*
- * @param globalChanges The changes to the global configuration.
- * @param preserveWindow If the activity window should be preserved on screen if the activity
- * is relaunched.
* @param ignoreVisibility If we should try to relaunch the activity even if it is invisible
* (stopped state). This is useful for the case where we know the
* activity will be visible soon and we want to ensure its configuration
@@ -9552,8 +9542,8 @@
* @return False if the activity was relaunched and true if it wasn't relaunched because we
* can't or the app handles the specific configuration that is changing.
*/
- boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
- boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
+ boolean ensureActivityConfiguration(boolean ignoreVisibility,
+ boolean isRequestedOrientationChanged) {
final Task rootTask = getRootTask();
if (rootTask.mConfigWillChange) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9667,10 +9657,21 @@
if (shouldRelaunchLocked(changes, mTmpConfig)) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
- startFreezingScreenLocked(globalChanges);
+ if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
+ && !mTransitionController.isShellTransitionsEnabled()) {
+ startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+ }
+ final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
+ != getWindowConfiguration().getDisplayRotation()
+ || !mTmpConfig.windowConfiguration.getMaxBounds().equals(
+ getWindowConfiguration().getMaxBounds());
+ final boolean isAppResizeOnly = !displayMayChange
+ && (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
+ | CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
// Do not preserve window if it is freezing screen because the original window won't be
// able to update drawn state that causes freeze timeout.
- preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
+ // TODO(b/258618073): Always preserve if possible.
+ final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
@@ -9834,11 +9835,6 @@
return changes;
}
- private static boolean isResizeOnlyChange(int change) {
- return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
- | CONFIG_SCREEN_LAYOUT)) == 0;
- }
-
private static boolean hasResizeChange(int change) {
return (change & (CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
| CONFIG_SCREEN_LAYOUT)) != 0;
@@ -9882,8 +9878,6 @@
task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
}
- startFreezingScreenLocked(0);
-
try {
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cb2adbc..d90d017 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -73,7 +73,6 @@
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
@@ -1733,6 +1732,7 @@
// So disallow the transient hide activity to move itself to front, e.g. trampoline.
if (!avoidMoveToFront() && (mService.mHomeProcess == null
|| mService.mHomeProcess.mUid != realCallingUid)
+ && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents())
&& r.mTransitionController.isTransientHide(targetTask)) {
mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
}
@@ -1859,8 +1859,7 @@
// over is removed.
// Passing {@code null} as the start parameter ensures all activities are made
// visible.
- mTargetRootTask.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, !PRESERVE_WINDOWS);
+ mTargetRootTask.ensureActivitiesVisible(null /* starting */);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetRootTask.mDisplayContent.executeAppTransition();
@@ -2867,7 +2866,7 @@
mRootWindowContainer.resumeFocusedTasksTopActivities(mTargetRootTask, null,
mOptions, mTransientLaunch);
} else {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
} else {
ActivityOptions.abort(mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dbae29b..f43c1b0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -118,7 +118,6 @@
import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
@@ -496,16 +495,13 @@
final UpdateConfigurationResult mTmpUpdateConfigurationResult =
new UpdateConfigurationResult();
+ // TODO(b/258618073): Remove this and make the related methods return whether config is changed.
static final class UpdateConfigurationResult {
// Configuration changes that were updated.
int changes;
// If the activity was relaunched to match the new configuration.
boolean activityRelaunched;
-
- void reset() {
- changes = 0;
- activityRelaunched = false;
- }
+ boolean mIsUpdating;
}
/** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -3834,8 +3830,7 @@
Settings.System.clearConfiguration(values);
}
updateConfigurationLocked(values, null, false, false /* persistent */,
- UserHandle.USER_NULL, false /* deferResume */,
- mTmpUpdateConfigurationResult);
+ UserHandle.USER_NULL, false /* deferResume */);
return mTmpUpdateConfigurationResult.changes != 0;
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4507,12 +4502,6 @@
}
}
- private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
- boolean initLocale, boolean persistent, int userId, boolean deferResume) {
- return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
- deferResume, null /* result */);
- }
-
/**
* Do either or both things: (1) change the current configuration, and (2)
* make sure the given activity is running with the (now) current
@@ -4524,8 +4513,7 @@
* for that particular user
*/
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
- boolean initLocale, boolean persistent, int userId, boolean deferResume,
- ActivityTaskManagerService.UpdateConfigurationResult result) {
+ boolean initLocale, boolean persistent, int userId, boolean deferResume) {
int changes = 0;
boolean kept = true;
@@ -4533,19 +4521,18 @@
try {
if (values != null) {
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
+ mTmpUpdateConfigurationResult.changes = changes;
+ mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
+ mTmpUpdateConfigurationResult.mIsUpdating = false;
continueWindowLayout();
}
-
- if (result != null) {
- result.changes = changes;
- result.activityRelaunched = !kept;
- }
+ mTmpUpdateConfigurationResult.activityRelaunched = !kept;
return kept;
}
@@ -5325,12 +5312,10 @@
}
if (starting != null) {
- kept = starting.ensureActivityConfiguration(changes,
- false /* preserveWindow */);
+ kept = starting.ensureActivityConfiguration();
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
- mRootWindowContainer.ensureActivitiesVisible(starting, changes,
- !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible(starting);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 10efb94..1872f6e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1462,7 +1462,7 @@
}
mLaunchingActivityWakeLock.release();
}
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
// Atomically retrieve all of the other things to do.
@@ -1603,7 +1603,7 @@
*/
rootTask.cancelAnimation();
rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
- rootTask.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ rootTask.ensureActivitiesVisible(null /* starting */);
activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
true /* processPausingActivities */, null /* configuration */);
@@ -1622,7 +1622,7 @@
// Follow on the workaround: activities are kept force hidden till the new windowing
// mode is set.
rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
- mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.resumeFocusedTasksTopActivities();
} finally {
mService.continueWindowLayout();
@@ -2026,7 +2026,7 @@
final Task rootTask = r.getRootTask();
if (rootTask.getDisplayArea().allResumedActivitiesComplete()) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
// Make sure activity & window visibility should be identical
// for all displays in this stage.
mRootWindowContainer.executeAppTransitionForAllDisplay();
@@ -2042,7 +2042,7 @@
mRecentTasks.add(task);
mService.getTaskChangeNotificationController().notifyTaskStackChanged();
- rootTask.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ rootTask.ensureActivitiesVisible(null /* starting */);
// When launching tasks behind, update the last active time of the top task after the new
// task has been shown briefly
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 68d13cd..6ed8967 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -91,6 +91,13 @@
/** Non-zero if this controller is triggered by shell transition. */
private final @TransitionOp int mTransitionOp;
+ /**
+ * Whether {@link #setupStartTransaction} is called when the transition is ready.
+ * If this is never set for {@link #OP_CHANGE}, the display may be changed to original state
+ * before the transition is ready, then this controller should be finished.
+ */
+ private boolean mIsStartTransactionPrepared;
+
/** Whether the start transaction of the transition is committed (by shell). */
private boolean mIsStartTransactionCommitted;
@@ -226,7 +233,8 @@
void updateTargetWindows() {
if (mTransitionOp == OP_LEGACY) return;
if (!mIsStartTransactionCommitted) {
- if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+ if ((mTimeoutRunnable == null || !mIsStartTransactionPrepared)
+ && !mDisplayContent.hasTopFixedRotationLaunchingApp()
&& !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
Slog.d(TAG, "Cancel for no change");
mDisplayContent.finishAsyncRotationIfPossible();
@@ -401,9 +409,18 @@
if (mTimeoutRunnable == null) {
mTimeoutRunnable = () -> {
synchronized (mService.mGlobalLock) {
- Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
- ? " start transaction is not committed" : mTargetWindowTokens));
+ final String reason;
if (!mIsStartTransactionCommitted) {
+ if (!mIsStartTransactionPrepared) {
+ reason = "setupStartTransaction is not called";
+ } else {
+ reason = "start transaction is not committed";
+ }
+ } else {
+ reason = "unfinished windows " + mTargetWindowTokens;
+ }
+ Slog.i(TAG, "Async rotation timeout: " + reason);
+ if (!mIsStartTransactionCommitted && mIsStartTransactionPrepared) {
// The transaction commit timeout will be handled by:
// 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then
// apply the start transaction of transition.
@@ -558,6 +575,7 @@
}
}
});
+ mIsStartTransactionPrepared = true;
}
/** Called when the start transition is ready, but it is not applied in time. */
@@ -577,6 +595,10 @@
/** Called when the transition by shell is done. */
void onTransitionFinished() {
if (mTransitionOp == OP_CHANGE) {
+ if (mTargetWindowTokens.isEmpty()) {
+ // If nothing was handled, then complete with the transition.
+ mDisplayContent.finishAsyncRotationIfPossible();
+ }
// With screen rotation animation, the windows are always faded in when they are drawn.
// Because if they are drawn fast enough, the fade animation should not be observable.
return;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c3f1e41..22d17b5 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1614,7 +1614,7 @@
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
activity.mTaskSupervisor.mStoppingActivities.remove(activity);
activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */, true);
+ true /* notifyClients */);
}
private static void restoreLaunchBehind(@NonNull ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 2e47677..e4eb7b3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -74,13 +74,13 @@
}
/**
- * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+ * Similar to {@link #scheduleTransactionItem}, but it sends the transaction immediately and
+ * it can be called without WM lock.
*
* @see WindowProcessController#setReportedProcState(int)
*/
- void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+ void scheduleTransactionItemNow(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
- // Immediately dispatching to client, and must not access WMS.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
if (transactionItem.isActivityLifecycleItem()) {
clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f8dc9c7..e7ecf52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -779,7 +779,7 @@
/**
* Used to prevent recursions when calling
- * {@link #ensureActivitiesVisible(ActivityRecord, int, boolean, boolean)}
+ * {@link #ensureActivitiesVisible(ActivityRecord, boolean)}
*/
private boolean mInEnsureActivitiesVisible = false;
@@ -1713,7 +1713,7 @@
if (handled && requestingContainer instanceof ActivityRecord) {
final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
- false /* deferResume */, null /* result */);
+ false /* deferResume */);
if (!kept) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
@@ -1721,7 +1721,7 @@
// We have a new configuration to push so we need to update ATMS for now.
// TODO: Clean up display configuration push between ATMS and WMS after unification.
updateDisplayOverrideConfigurationLocked(config, null /* starting */,
- false /* deferResume */, null);
+ false /* deferResume */);
}
return handled;
}
@@ -6333,7 +6333,7 @@
Settings.System.clearConfiguration(values);
updateDisplayOverrideConfigurationLocked(values, null /* starting */,
- false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
+ false /* deferResume */);
return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
}
@@ -6342,8 +6342,7 @@
* new one will be computed in WM based on current display info.
*/
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
- ActivityRecord starting, boolean deferResume,
- ActivityTaskManagerService.UpdateConfigurationResult result) {
+ ActivityRecord starting, boolean deferResume) {
int changes = 0;
boolean kept = true;
@@ -6361,19 +6360,19 @@
} else {
changes = performDisplayOverrideConfigUpdate(values);
}
+ mAtmService.mTmpUpdateConfigurationResult.changes = changes;
+ mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
+ mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
mAtmService.continueWindowLayout();
}
- if (result != null) {
- result.changes = changes;
- result.activityRelaunched = !kept;
- }
+ mAtmService.mTmpUpdateConfigurationResult.activityRelaunched = !kept;
return kept;
}
@@ -6569,8 +6568,7 @@
}
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
if (mInEnsureActivitiesVisible) {
// Don't do recursive work.
return;
@@ -6579,8 +6577,7 @@
try {
mInEnsureActivitiesVisible = true;
forAllRootTasks(rootTask -> {
- rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ rootTask.ensureActivitiesVisible(starting, notifyClients);
});
if (mTransitionController.useShellTransitionsRotation()
&& mTransitionController.isCollecting()
@@ -6619,7 +6616,7 @@
if (!wasTransitionSet) {
prepareAppTransition(TRANSIT_NONE);
}
- mRootWindowContainer.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ mRootWindowContainer.ensureActivitiesVisible();
// If there was a transition set already we don't want to interfere with it as we might be
// starting it too early.
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 9cc311d..f40eb24 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -33,8 +33,6 @@
private boolean mAboveTop;
private boolean mContainerShouldBeVisible;
private boolean mBehindFullyOccludedContainer;
- private int mConfigChanges;
- private boolean mPreserveWindows;
private boolean mNotifyClients;
EnsureActivitiesVisibleHelper(TaskFragment container) {
@@ -45,14 +43,10 @@
* Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
*
* @param starting The activity that is being started
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating.
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
- void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
- boolean notifyClients) {
+ void reset(ActivityRecord starting, boolean notifyClients) {
mStarting = starting;
mTopRunningActivity = mTaskFragment.topRunningActivity();
// If the top activity is not fullscreen, then we need to make sure any activities under it
@@ -60,33 +54,26 @@
mAboveTop = mTopRunningActivity != null;
mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
- mConfigChanges = configChanges;
- mPreserveWindows = preserveWindows;
mNotifyClients = notifyClients;
}
/**
* Update and commit visibility with an option to also update the configuration of visible
* activities.
- * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
- * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @see Task#ensureActivitiesVisible(ActivityRecord)
+ * @see RootWindowContainer#ensureActivitiesVisible()
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
*
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating.
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
- void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
- boolean notifyClients) {
- reset(starting, configChanges, preserveWindows, notifyClients);
+ void process(@Nullable ActivityRecord starting, boolean notifyClients) {
+ reset(starting, notifyClients);
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity
- + " configChanges=0x" + Integer.toHexString(configChanges));
+ Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity);
}
if (mTopRunningActivity != null && mTaskFragment.asTask() != null) {
// TODO(14709632): Check if this needed to be implemented in TaskFragment.
@@ -107,8 +94,7 @@
final TaskFragment childTaskFragment = child.asTaskFragment();
if (childTaskFragment != null
&& childTaskFragment.getTopNonFinishingActivity() != null) {
- childTaskFragment.updateActivityVisibilities(starting, configChanges,
- preserveWindows, notifyClients);
+ childTaskFragment.updateActivityVisibilities(starting, notifyClients);
// The TaskFragment should fully occlude the activities below if the bounds
// equals to its parent task, unless it is translucent.
mBehindFullyOccludedContainer |=
@@ -188,13 +174,11 @@
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != mStarting && mNotifyClients) {
- r.ensureActivityConfiguration(0 /* globalChanges */, mPreserveWindows,
- true /* ignoreVisibility */);
+ r.ensureActivityConfiguration(true /* ignoreVisibility */);
}
if (!r.attachedToProcess()) {
- makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
- resumeTopActivity && isTop, r);
+ makeVisibleAndRestartIfNeeded(mStarting, resumeTopActivity && isTop, r);
} else if (r.isVisibleRequested()) {
// If this activity is already visible, then there is nothing to do here.
if (DEBUG_VISIBILITY) {
@@ -213,8 +197,6 @@
} else {
r.makeVisibleIfNeeded(mStarting, mNotifyClients);
}
- // Aggregate current change flags.
- mConfigChanges |= r.configChangeFlags;
} else {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Make invisible? " + r
@@ -242,16 +224,13 @@
}
}
- private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+ private void makeVisibleAndRestartIfNeeded(ActivityRecord starting,
boolean andResume, ActivityRecord r) {
// This activity needs to be visible, but isn't even running...
// get it started and resume if no other root task in this root task is resumed.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
}
- if (r != starting) {
- r.startFreezingScreenLocked(configChanges);
- }
if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8cf4713..a84ebd9 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -167,10 +167,10 @@
/** {@inheritDoc} */
@Override
- public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags) {
+ public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags) {
return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
- displayId, whenNanos, policyFlags);
+ displayId, source, action, whenNanos, policyFlags);
}
/**
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cbc7b83..6d11804 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -43,7 +43,6 @@
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_GOING_AWAY;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_PER_DISPLAY;
@@ -239,7 +238,7 @@
// Update the sleep token first such that ensureActivitiesVisible has correct sleep token
// state when evaluating visibilities.
updateKeyguardSleepToken();
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */,
displayId);
setWakeTransitionReady();
@@ -291,7 +290,7 @@
// Some stack visibility might change (e.g. docked stack)
mRootWindowContainer.resumeFocusedTasksTopActivities();
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.addStartingWindowsForVisibleActivities();
mWindowManager.executeAppTransition();
} finally {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9305396..97cc982 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -46,6 +46,7 @@
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
@@ -1185,6 +1186,7 @@
mUserAspectRatio = getUserMinAspectRatioOverrideCode();
return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+ && mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
&& mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 5269d35..7b23004 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -28,7 +28,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -126,8 +125,7 @@
// The activity may be relaunched if it cannot handle the current configuration
// changes. The activity will be paused state if it is relaunched, otherwise it
// keeps the original stopped state.
- targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */, true /* ignoreVisibility */);
+ targetActivity.ensureActivityConfiguration(true /* ignoreVisibility */);
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s",
targetActivity.getConfiguration());
}
@@ -261,7 +259,7 @@
// If we updated the launch-behind state, update the visibility of the activities after
// we fetch the visible tasks to be controlled by the animation
- mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mService.mRootWindowContainer.ensureActivitiesVisible();
ActivityOptions options = null;
if (eventTime > 0) {
@@ -380,8 +378,7 @@
// transition (the target activity will be one of closing apps).
if (!controller.shouldDeferCancelWithScreenshot()
&& !targetRootTask.isFocusedRootTaskOnDisplay()) {
- targetRootTask.ensureActivitiesVisible(null /* starting */,
- 0 /* starting */, false /* preserveWindows */);
+ targetRootTask.ensureActivitiesVisible(null /* starting */);
}
// Keep target root task in place, nothing changes, so ignore the transition
// logic below
@@ -389,7 +386,7 @@
}
mWindowManager.prepareAppTransitionNone();
- mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, false);
+ mService.mRootWindowContainer.ensureActivitiesVisible();
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
// No reason to wait for the pausing activity in this case, as the hiding of
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a75dae..ca66a66 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -63,7 +63,6 @@
import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
@@ -1753,8 +1752,7 @@
// activities are affecting configuration now.
// Passing null here for 'starting' param value, so that visibility of actual starting
// activity will be properly updated.
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, false /* notifyClients */);
+ ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
if (displayId == INVALID_DISPLAY) {
// The caller didn't provide a valid display id, skip updating config.
@@ -1778,7 +1776,7 @@
if (displayContent != null) {
// Update the configuration of the activities on the display.
return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
- deferResume, null /* result */);
+ deferResume);
} else {
return true;
}
@@ -1865,16 +1863,18 @@
* Make sure that all activities that need to be visible in the system actually are and update
* their configuration.
*/
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows) {
- ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+ void ensureActivitiesVisible() {
+ ensureActivitiesVisible(null /* starting */);
+ }
+
+ void ensureActivitiesVisible(ActivityRecord starting) {
+ ensureActivitiesVisible(starting, true /* notifyClients */);
}
/**
- * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @see #ensureActivitiesVisible()
*/
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
if (mTaskSupervisor.inActivityVisibilityUpdate()
|| mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
// Don't do recursive work.
@@ -1885,8 +1885,7 @@
// First the front root tasks. In case any are not fullscreen and are in front of home.
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent display = getChildAt(displayNdx);
- display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ display.ensureActivitiesVisible(starting, notifyClients);
}
} finally {
mTaskSupervisor.endActivityVisibilityUpdate();
@@ -2237,7 +2236,7 @@
try {
if (localVisibilityDeferred) {
mTaskSupervisor.setDeferRootVisibilityUpdate(false);
- ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ ensureActivitiesVisible();
}
} finally {
transitionController.continueTransitionReady();
@@ -2370,7 +2369,7 @@
// It may be nothing to resume because there are pausing activities or all the top
// activities are resumed. Then it still needs to make sure all visible activities are
// running in case the tasks were reordered or there are non-top visible activities.
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS);
+ ensureActivitiesVisible();
}
}
@@ -2542,8 +2541,7 @@
// display orientation can be updated first if needed. Otherwise there may
// have redundant configuration changes due to apply outdated display
// orientation (from keyguard) to activity.
- rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
}
});
}
@@ -2885,8 +2883,7 @@
if (allowDelay) {
result[0] &= task.goToSleepIfPossible(shuttingDown);
} else {
- task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS);
+ task.ensureActivitiesVisible(null /* starting */);
}
});
return result[0];
@@ -3774,8 +3771,7 @@
}
}
if (!mHasActivityStarted) {
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ ensureActivitiesVisible();
}
return mHasActivityStarted;
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7995028..ed54ea8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -771,6 +771,7 @@
if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
mService.dispatchNewAnimatorScaleLocked(this);
}
+ mProcess.mWindowSession = this;
}
mAddedWindows.add(w);
}
@@ -782,6 +783,9 @@
}
}
+ boolean hasWindow() {
+ return !mAddedWindows.isEmpty();
+ }
void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
boolean visible, int type) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dbfcc22..c674176 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -90,7 +90,6 @@
import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_TASK_MSG;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -760,7 +759,7 @@
return;
}
mResizeMode = resizeMode;
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.resumeFocusedTasksTopActivities();
updateTaskDescription();
}
@@ -801,15 +800,14 @@
if (setBounds(bounds, forced) != BOUNDS_CHANGE_NONE) {
final ActivityRecord r = topRunningActivityLocked();
if (r != null) {
- kept = r.ensureActivityConfiguration(0 /* globalChanges */,
- preserveWindow);
+ kept = r.ensureActivityConfiguration();
// Preserve other windows for resizing because if resizing happens when there
// is a dialog activity in the front, the activity that still shows some
// content to the user will become black and cause flickers. Note in most cases
// this won't cause tons of irrelevant windows being preserved because only
// activities in this task may experience a bounds change. Configs for other
// activities stay the same.
- mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow);
+ mRootWindowContainer.ensureActivitiesVisible(r);
if (!kept) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
@@ -915,7 +913,7 @@
if (!deferResume) {
// The task might have already been running and its visibility needs to be synchronized
// with the visibility of the root task / windows.
- root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ root.ensureActivitiesVisible();
root.resumeFocusedTasksTopActivities();
}
@@ -4752,7 +4750,7 @@
}
if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
}
@@ -4793,8 +4791,7 @@
mRootWindowContainer.resumeFocusedTasksTopActivities();
// Update visibility of activities before notifying WM. This way it won't try to resize
// windows that are no longer visible.
- mRootWindowContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
final boolean isOnHomeDisplay() {
@@ -4938,41 +4935,27 @@
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating
- * configuration in {@link EnsureActivitiesVisibleHelper}.
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen as part of
- * {@link EnsureActivitiesVisibleHelper}.
- *
*/
- void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
- boolean preserveWindows) {
- ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting) {
+ ensureActivitiesVisible(starting, true /* notifyClients */);
}
/**
* Ensure visibility with an option to also update the configuration of visible activities.
- * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
- * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @see #ensureActivitiesVisible(ActivityRecord)
+ * @see RootWindowContainer#ensureActivitiesVisible()
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
* @param notifyClients Flag indicating whether the visibility updates should be sent to the
* clients in {@link EnsureActivitiesVisibleHelper}.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating
- * configuration in {@link EnsureActivitiesVisibleHelper}.
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen as part of
- * {@link EnsureActivitiesVisibleHelper}.
*/
// TODO: Should be re-worked based on the fact that each task as a root task in most cases.
- void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting, boolean notifyClients) {
mTaskSupervisor.beginActivityVisibilityUpdate();
try {
forAllLeafTasks(task -> {
- task.updateActivityVisibilities(starting, configChanges, preserveWindows,
- notifyClients);
+ task.updateActivityVisibilities(starting, notifyClients);
}, true /* traverseTopToBottom */);
if (mTranslucentActivityWaiting != null &&
@@ -5273,7 +5256,7 @@
// tell WindowManager that r is visible even though it is at the back of the root
// task.
r.setVisibility(true);
- ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ ensureActivitiesVisible(null /* starting */);
// If launching behind, the app will start regardless of what's above it, so mark it
// as unknown even before prior `pause`. This also prevents a race between set-ready
// and activityPause. Launch-behind is basically only used for dream now.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c57983c..90a3b253 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1777,13 +1777,11 @@
void onRootTaskOrderChanged(Task rootTask);
}
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
try {
forAllRootTasks(rootTask -> {
- rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ rootTask.ensureActivitiesVisible(starting, notifyClients);
});
} finally {
mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d01912..d425bdf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -57,7 +57,6 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
@@ -950,8 +949,7 @@
}
if (shouldSleep) {
- updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS, true /* notifyClients */);
+ updateActivityVisibilities(null /* starting */, true /* notifyClients */);
}
return shouldSleep;
@@ -1218,12 +1216,11 @@
return top != null && top.mLaunchTaskBehind;
}
- final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ final void updateActivityVisibilities(@Nullable ActivityRecord starting,
+ boolean notifyClients) {
mTaskSupervisor.beginActivityVisibilityUpdate();
try {
- mEnsureActivitiesVisibleHelper.process(
- starting, configChanges, preserveWindows, notifyClients);
+ mEnsureActivitiesVisibleHelper.process(starting, notifyClients);
} finally {
mTaskSupervisor.endActivityVisibilityUpdate();
}
@@ -1249,8 +1246,7 @@
if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.allResumedActivitiesComplete()) {
// Ensure the visibility gets updated before execute app transition.
- taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, true /* notifyClients */);
+ taskDisplayArea.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
@@ -1907,7 +1903,7 @@
prev.resumeKeyDispatchingLocked();
}
- mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible(resuming);
// Notify when the task stack has changed, but only if visibilities changed (not just
// focus). Also if there is an active root pinned task - we always want to notify it about
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3117db5..b12855e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1136,8 +1136,7 @@
// The transient hide tasks could be occluded now, e.g. returning to home. So trigger
// the update to make the activities in the tasks invisible-requested, then the next
// step can continue to commit the visibility.
- mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, true /* preserveWindows */);
+ mController.mAtm.mRootWindowContainer.ensureActivitiesVisible();
// Record all the now-hiding activities so that they are committed. Just use
// mParticipants because we can avoid a new list this way.
for (int i = 0; i < mTransientHideTasks.size(); ++i) {
@@ -2863,8 +2862,7 @@
* check whether to deliver the new configuration to clients.
*/
@Nullable
- ArrayList<ActivityRecord> applyDisplayChangeIfNeeded() {
- ArrayList<ActivityRecord> activitiesMayChange = null;
+ void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) {
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = mParticipants.valueAt(i);
final DisplayContent dc = wc.asDisplayContent();
@@ -2881,18 +2879,13 @@
// If the update is deferred, sendNewConfiguration won't deliver new configuration to
// clients, then it is the caller's responsibility to deliver the changes.
if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
- if (activitiesMayChange == null) {
- activitiesMayChange = new ArrayList<>();
- }
- final ArrayList<ActivityRecord> visibleActivities = activitiesMayChange;
dc.forAllActivities(r -> {
if (r.isVisibleRequested()) {
- visibleActivities.add(r);
+ activitiesMayChange.add(r);
}
});
}
}
- return activitiesMayChange;
}
boolean getLegacyIsReady() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 89a70e5..7b0d931 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@
/* Start Available Flags */
- final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
-
final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c1310a6..dda33f3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3105,7 +3105,7 @@
try {
synchronized (mGlobalLock) {
if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
- mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ mRoot.ensureActivitiesVisible();
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4b99432..9e4a31c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -65,7 +65,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -571,14 +570,15 @@
mService.deferWindowLayout();
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
try {
- final ArrayList<ActivityRecord> activitiesMayChange =
- transition != null ? transition.applyDisplayChangeIfNeeded() : null;
- if (activitiesMayChange != null) {
- effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
+ if (transition != null) {
+ transition.applyDisplayChangeIfNeeded(haveConfigChanges);
+ if (!haveConfigChanges.isEmpty()) {
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
}
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
- final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
@@ -626,7 +626,7 @@
// When removing pip, make sure that onStop is sent to the app ahead of
// onPictureInPictureModeChanged.
// See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
- wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ wc.asTask().ensureActivitiesVisible(null /* starting */);
wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(
null /* launchedActivity */, false /* processPausingActivities */,
"force-stop-on-removing-pip");
@@ -692,29 +692,16 @@
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
// Already calls ensureActivityConfig
- mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mService.mRootWindowContainer.ensureActivitiesVisible();
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
} else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
haveConfigChanges.valueAt(i).forAllActivities(r -> {
- r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
- if (activitiesMayChange != null) {
- activitiesMayChange.remove(r);
+ if (r.isVisibleRequested()) {
+ r.ensureActivityConfiguration(true /* ignoreVisibility */);
}
});
}
- // TODO(b/258618073): Combine with haveConfigChanges after confirming that there
- // is no problem to always preserve window. Currently this uses the parameters
- // as ATMS#ensureConfigAndVisibilityAfterUpdate.
- if (activitiesMayChange != null) {
- for (int i = activitiesMayChange.size() - 1; i >= 0; --i) {
- final ActivityRecord ar = activitiesMayChange.get(i);
- if (!ar.isVisibleRequested()) continue;
- ar.ensureActivityConfiguration(0 /* globalChanges */,
- !PRESERVE_WINDOWS, true /* ignoreVisibility */,
- false /* isRequestedOrientationChanged */);
- }
- }
}
if (effects != 0) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 5721750..b8fa5e5 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -188,6 +188,10 @@
// Set to true when process was launched with a wrapper attached
private volatile boolean mUsingWrapper;
+ /** Non-null if this process may have a window. */
+ @Nullable
+ Session mWindowSession;
+
// Thread currently set for VR scheduling
int mVrThreadTid;
@@ -399,7 +403,7 @@
// the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
try {
// No WM lock here.
- mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+ mAtm.getLifecycleManager().scheduleTransactionItemNow(
thread, configurationChangeItem);
} catch (Exception e) {
Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
@@ -989,7 +993,7 @@
if (packageName.equals(r.packageName)
&& r.applyAppSpecificConfig(nightMode, localesOverride, gender)
&& r.isVisibleRequested()) {
- r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
+ r.ensureActivityConfiguration();
}
}
}
@@ -1675,7 +1679,12 @@
private void scheduleClientTransactionItem(@NonNull IApplicationThread thread,
@NonNull ClientTransactionItem transactionItem) {
try {
- mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+ if (mWindowSession != null && mWindowSession.hasWindow()) {
+ mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+ } else {
+ // Non-UI process can handle the change directly.
+ mAtm.getLifecycleManager().scheduleTransactionItemNow(thread, transactionItem);
+ }
} catch (DeadObjectException e) {
// Expected if the process has been killed.
Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
@@ -1723,7 +1732,7 @@
overrideConfig.assetsSeq = assetSeq;
r.onRequestedOverrideConfigurationChanged(overrideConfig);
if (r.isVisibleRequested()) {
- r.ensureActivityConfiguration(0, true);
+ r.ensureActivityConfiguration();
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 32638e0..58ade1b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -252,6 +252,7 @@
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -3675,7 +3676,7 @@
markRedrawForSyncReported();
- if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+ if (Flags.bundleClientTransactionFlag()) {
getProcess().scheduleClientTransactionItem(
WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0dd0564..9ba0a2a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -358,8 +358,8 @@
void notifyVibratorState(int32_t deviceId, bool isOn) override;
bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
- void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
- uint32_t& policyFlags) override;
+ void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
+ nsecs_t when, uint32_t& policyFlags) override;
nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
@@ -1496,7 +1496,8 @@
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
+ int32_t action, nsecs_t when,
uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
@@ -1525,7 +1526,7 @@
const jint wmActions =
env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
- displayId, when, policyFlags);
+ displayId, source, action, when, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
return;
}
@@ -2943,7 +2944,7 @@
"interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");
GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, clazz,
- "interceptMotionBeforeQueueingNonInteractive", "(IJI)I");
+ "interceptMotionBeforeQueueingNonInteractive", "(IIIJI)I");
GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
"interceptKeyBeforeDispatching",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7..1185a4e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@
// AdServicesManagerService (PP API service)
t.traceBegin("StartAdServicesManagerService");
- SystemService adServices = mSystemServiceManager
- .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
- if (adServices instanceof Dumpable) {
- mDumper.addDumpable((Dumpable) adServices);
- }
+ mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
t.traceEnd();
// OnDevicePersonalizationSystemService
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 8d8274c..87fc7a4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -122,6 +122,16 @@
}
@Test
+ public void testRegisterHdrListener_ZeroMinHdrPercent() {
+ IBinder otherBinder = mock(IBinder.class);
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT,
+ /* minimumHdrPercentOfScreen= */ 0, otherBinder);
+
+ verify(mMockHdrInfoListener).unregister(mMockBinder);
+ verify(mMockHdrInfoListener).register(otherBinder);
+ }
+
+ @Test
public void testRegisterNotCalledIfHbmConfigIsMissing() {
IBinder otherBinder = mock(IBinder.class);
mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003d..32878b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
@@ -97,6 +100,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -140,6 +144,9 @@
private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
private static final String MISSING_PERMISSION = "missing_permission";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Random mRandom;
@Mock
@@ -1347,6 +1354,24 @@
assertThat(mManager.isVisibleToCaller()).isFalse();
}
+ @Test
+ public void testValidateLocation_futureLocation() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+ Location location = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(location);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+
+ Location futureLocation = createLocation(NAME, mRandom);
+ futureLocation.setElapsedRealtimeNanos(
+ SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+ mProvider.setProviderLocation(futureLocation);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 6c44fd0..60cedcf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -44,6 +44,7 @@
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.function.BiFunction;
/**
* A unit test for PackageMonitorCallbackHelper implementation.
@@ -78,7 +79,8 @@
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -91,7 +93,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
- null /* broadcastAllowList */, mHandler);
+ null /* broadcastAllowList */, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
@@ -99,12 +101,41 @@
mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@Test
+ public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
+ Bundle result = new Bundle();
+ result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+ result.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[]{FAKE_PACKAGE_NAME});
+ BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> result;
+
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+ null /* broadcastAllowList */, mHandler, filterExtras);
+
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+ bundleCaptor.capture());
+ Bundle bundle = bundleCaptor.getValue();
+ Intent intent = bundle.getParcelable(
+ PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGES_SUSPENDED);
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ assertThat(pkgList).isNotNull();
+ assertThat(pkgList.length).isEqualTo(1);
+ assertThat(pkgList[0]).isEqualTo(FAKE_PACKAGE_NAME);
+ }
+
+ @Test
public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception {
IRemoteCallback callback = createMockPackageMonitorCallback();
@@ -112,7 +143,8 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -136,7 +168,8 @@
// Notify for user 10
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -239,7 +272,8 @@
mPackageMonitorCallbackHelper.onUserRemoved(10);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -255,7 +289,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList, mHandler);
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
@@ -271,7 +305,7 @@
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList, mHandler);
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -287,7 +321,7 @@
Process.SYSTEM_UID);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList, mHandler);
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c9e1c4a..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,21 +16,30 @@
package com.android.server.biometrics.sensors.face;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -42,6 +51,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.biometrics.Flags;
@@ -66,6 +76,7 @@
private static final int ID_VIRTUAL = 6;
private static final String NAME_DEFAULT = "default";
private static final String NAME_VIRTUAL = "virtual";
+ private static final String OP_PACKAGE_NAME = "FaceServiceTest/SystemUi";
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -78,15 +89,19 @@
@Rule
public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Mock
- FaceProvider mFaceProviderDefault;
+ private FaceProvider mFaceProviderDefault;
@Mock
- FaceProvider mFaceProviderVirtual;
+ private FaceProvider mFaceProviderVirtual;
@Mock
- IFace mDefaultFaceDaemon;
+ private IFace mDefaultFaceDaemon;
@Mock
- IFace mVirtualFaceDaemon;
+ private IFace mVirtualFaceDaemon;
@Mock
- IBiometricService mIBiometricService;
+ private IBiometricService mIBiometricService;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private IFaceServiceReceiver mFaceServiceReceiver;
private final SensorProps mDefaultSensorProps = new SensorProps();
private final SensorProps mVirtualSensorProps = new SensorProps();
@@ -117,7 +132,13 @@
new SensorProps[]{mVirtualSensorProps});
when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+ when(mFaceProviderDefault.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+ when(mFaceProviderVirtual.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+ mContext.getTestablePermissions().setPermission(
+ USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
(name) -> {
@@ -136,7 +157,13 @@
if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
return null;
- }, () -> mIBiometricService);
+ }, () -> mIBiometricService,
+ (name) -> {
+ if (NAME_DEFAULT.equals(name)) return mFaceProviderDefault;
+ if (NAME_VIRTUAL.equals(name)) return mFaceProviderVirtual;
+ return null;
+ },
+ () -> new String[]{NAME_DEFAULT, NAME_VIRTUAL});
}
@Test
@@ -191,6 +218,39 @@
eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
}
+ @Test
+ public void testOptionsForAuthentication() throws Exception {
+ FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+ .build();
+ initService();
+ mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
+
+ final long operationId = 5;
+ mFaceService.mServiceWrapper.authenticate(mToken, operationId, mFaceServiceReceiver,
+ faceAuthenticateOptions);
+
+ assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
+ @Test
+ public void testOptionsForDetect() throws Exception {
+ FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+ .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
+ .getPackageName())
+ .build();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.config_keyguardComponent,
+ OP_PACKAGE_NAME);
+ initService();
+ mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
+ mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
+ faceAuthenticateOptions);
+
+ assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
private void waitForRegistration() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index f570ba2..88956b6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -40,6 +40,7 @@
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -61,6 +62,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
@@ -92,6 +94,7 @@
private static final String NAME_VIRTUAL = "virtual";
private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
List.of();
+ private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -343,6 +346,24 @@
assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid());
}
+ @Test
+ public void testOptionsForDetect() throws Exception {
+ FingerprintAuthenticateOptions fingerprintAuthenticateOptions =
+ new FingerprintAuthenticateOptions.Builder()
+ .setOpPackageName(ComponentName.unflattenFromString(
+ OP_PACKAGE_NAME).getPackageName())
+ .build();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.config_keyguardComponent,
+ OP_PACKAGE_NAME);
+ initServiceWithAndWait(NAME_DEFAULT);
+ mService.mServiceWrapper.detectFingerprint(mToken, mServiceReceiver,
+ fingerprintAuthenticateOptions);
+
+ assertThat(fingerprintAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
FingerprintProvider provider, long operationId) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 071d571..9b28b81 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -53,11 +53,11 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualCameraControllerTest {
- private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+ private static final String CAMERA_NAME_1 = "Virtual camera 1";
private static final int CAMERA_WIDTH_1 = 100;
private static final int CAMERA_HEIGHT_1 = 200;
- private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+ private static final String CAMERA_NAME_2 = "Virtual camera 2";
private static final int CAMERA_WIDTH_2 = 400;
private static final int CAMERA_HEIGHT_2 = 600;
private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
@@ -84,7 +84,7 @@
@Test
public void registerCamera_registersCamera() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -98,7 +98,7 @@
@Test
public void unregisterCamera_unregistersCamera() throws Exception {
VirtualCameraConfig config = createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
mVirtualCameraController.registerCamera(config);
mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +109,9 @@
@Test
public void close_unregistersAllCameras() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
mVirtualCameraController.close();
@@ -129,10 +129,10 @@
}
private VirtualCameraConfig createVirtualCameraConfig(
- int width, int height, int format, int displayNameResId) {
+ int width, int height, int format, String displayName) {
return new VirtualCameraConfig.Builder()
.addStreamConfig(width, height, format)
- .setDisplayNameStringRes(displayNameResId)
+ .setName(displayName)
.setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
.build();
}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index dc1d2c5..21b8a94 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,23 +16,26 @@
package com.android.server.os;
-import android.app.admin.flags.Flags;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
-
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.app.role.RoleManager;
import android.content.Context;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
import android.os.IBinder;
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,6 +51,8 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.util.concurrent.CompletableFuture;
@@ -66,6 +71,11 @@
private BugreportManagerServiceImpl mService;
private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+ @Mock
+ private UserManager mMockUserManager;
+ @Mock
+ private DevicePolicyManager mMockDevicePolicyManager;
+
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
private AtomicFile mMappingFile;
@@ -75,14 +85,17 @@
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
mService = new BugreportManagerServiceImpl(
- new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
- mMappingFile));
+ new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager));
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
+ // The calling user is an admin user by default.
+ when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
}
@After
@@ -165,6 +178,36 @@
}
@Test
+ public void testStartBugreport_throwsForNonAdminUser() throws Exception {
+ when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+
+ Exception thrown = assertThrows(Exception.class,
+ () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false));
+
+ assertThat(thrown.getMessage()).contains("not an admin user");
+ }
+
+ @Test
+ public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception {
+ when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+ when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1);
+ when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false);
+
+ Exception thrown = assertThrows(Exception.class,
+ () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_REMOTE,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false));
+
+ assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
+ }
+
+ @Test
public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
Listener listener = new Listener(latch);
@@ -207,7 +250,8 @@
private void clearAllowlist() {
mService = new BugreportManagerServiceImpl(
- new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile));
+ new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager));
}
private static class Listener implements IDumpstateListener {
@@ -258,4 +302,27 @@
complete(successful);
}
}
+
+ private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+
+ private final UserManager mUserManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+
+ TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
+ UserManager um, DevicePolicyManager dpm) {
+ super(context, allowlistedPackages, mappingFile);
+ mUserManager = um;
+ mDevicePolicyManager = dpm;
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public DevicePolicyManager getDevicePolicyManager() {
+ return mDevicePolicyManager;
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9408a8b..3ab7496 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -9258,41 +9258,46 @@
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+ public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
- Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
- SOURCE_CONTEXT);
- mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
-
Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
SOURCE_USER_ACTION);
- mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+ mBinderService.setAutomaticZenRuleState("id", withSourceUser);
+
verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
}
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+ public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
SOURCE_CONTEXT);
- assertThrows(IllegalArgumentException.class,
- () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
- /* fromUser= */ true));
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
- SOURCE_USER_ACTION);
- assertThrows(IllegalArgumentException.class,
- () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
- /* fromUser= */ false));
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
}
private ZenModeHelper setUpMockZenTest() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 718c598..e88a00b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -161,6 +161,7 @@
import com.android.internal.R;
import com.android.server.wm.ActivityRecord.State;
+import com.android.window.flags.Flags;
import org.junit.Assert;
import org.junit.Before;
@@ -320,7 +321,7 @@
}
private void ensureActivityConfiguration(ActivityRecord activity) {
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ activity.ensureActivityConfiguration();
}
@Test
@@ -718,7 +719,7 @@
// Clear size compat.
activity.clearSizeCompatMode();
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ activity.ensureActivityConfiguration();
mDisplayContent.sendNewConfiguration();
// Relaunching the app should still respect the orientation request.
@@ -819,8 +820,7 @@
task.onConfigurationChanged(newConfig);
- activity.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */, true /* ignoreVisibility */);
+ activity.ensureActivityConfiguration(true /* ignoreVisibility */);
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(activity.token,
@@ -1563,8 +1563,7 @@
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "true");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
topActivity.setShowWhenLocked(true);
// Verify the stack-top activity is occluded keyguard.
@@ -1624,7 +1623,6 @@
secondActivity.finishing = true;
secondActivity.completeFinishing("test");
verify(secondActivity.mDisplayContent).ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */ , false /* preserveWindows */,
true /* notifyClients */);
// Finish the first activity
@@ -1632,7 +1630,6 @@
firstActivity.setVisibleRequested(true);
firstActivity.completeFinishing("test");
verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */ , false /* preserveWindows */,
true /* notifyClients */);
// Remove the translucent activity and clear invocations for next test
@@ -1960,6 +1957,7 @@
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
+ activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -1970,14 +1968,12 @@
activity.finishFixedRotationTransform();
final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
assertNotNull(rotationAnim);
- rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
// Because the display doesn't rotate, the rotated activity needs to cancel the fixed
// rotation. There should be a rotation animation to cover the change of activity.
verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
assertTrue(activity.isFreezingScreen());
assertFalse(displayRotation.isRotatingSeamlessly());
- assertTrue(rotationAnim.isRotating());
// Simulate the remote rotation has completed and the configuration doesn't change, then
// the rotated activity should also be restored by clearing the transform.
@@ -3370,7 +3366,7 @@
// to client if the app didn't request IME visible.
assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
- if (mWm.mFlags.mWindowStateResizeItemFlag) {
+ if (Flags.bundleClientTransactionFlag()) {
verify(app2.getProcess()).scheduleClientTransactionItem(
isA(WindowStateResizeItem.class));
} else {
@@ -3697,7 +3693,7 @@
doReturn(false).when(activity).showToCurrentUser();
spyOn(taskFragment);
doReturn(false).when(taskFragment).shouldBeVisible(any());
- display.ensureActivitiesVisible(null, 0, false, false);
+ display.ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
assertFalse(activity.isVisibleRequested());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index c757457..09f677e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -172,7 +172,7 @@
@Test
public void testScheduleTransactionItemUnlocked() throws RemoteException {
// Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem);
+ mLifecycleManager.scheduleTransactionItemNow(mNonBinderClient, mTransactionItem);
// Dispatch immediately.
assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
@@ -217,6 +217,8 @@
@Test
public void testDispatchPendingTransactions() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction);
mLifecycleManager.dispatchPendingTransactions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfe79bf..6497ee9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -75,7 +75,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
@@ -490,7 +489,7 @@
newOverrideConfig.fontScale += 0.3;
defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig,
- null /* starting */, false /* deferResume */, null /* result */);
+ null /* starting */, false /* deferResume */);
// Check that global configuration is updated, as we've updated default display's config.
Configuration globalConfig = mWm.mRoot.getConfiguration();
@@ -499,7 +498,7 @@
// Return back to original values.
defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig,
- null /* starting */, false /* deferResume */, null /* result */);
+ null /* starting */, false /* deferResume */);
globalConfig = mWm.mRoot.getConfiguration();
assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
@@ -1176,7 +1175,7 @@
activity.setRequestedOrientation(newOrientation);
verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
- anyBoolean(), same(null));
+ anyBoolean());
assertEquals(ROTATION_180, dc.getRotation());
}
@@ -2123,10 +2122,8 @@
// Once transition starts, rotation is applied and transition shows DC rotating.
testPlayer.startTransition();
waitUntilHandlersIdle();
- verify(activity1).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
- anyBoolean());
- verify(activity2).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
- anyBoolean());
+ verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean());
+ verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean());
assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
assertNotNull(testPlayer.mLastReady);
assertTrue(testPlayer.mController.isPlaying());
@@ -2248,11 +2245,11 @@
// The assertion will fail if DisplayArea#ensureActivitiesVisible is called twice.
assertFalse(called[0]);
called[0] = true;
- mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+ mDisplayContent.ensureActivitiesVisible(null, false);
return null;
- }).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean());
+ }).when(mockTda).ensureActivitiesVisible(any(), anyBoolean());
- mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+ mDisplayContent.ensureActivitiesVisible(null, false);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 8de45b0..32b3558 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -106,8 +106,7 @@
topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
mRecentsComponent, true /* getRecentsAnimation */);
@@ -178,8 +177,7 @@
mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
null /* recentsAnimationRunner */);
- verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
- anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */);
+ verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
}
@@ -199,8 +197,7 @@
"testRestartRecentsActivity");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
@@ -354,8 +351,7 @@
doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId();
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(),
true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 89cd726..b5883b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -823,8 +823,7 @@
.build();
doReturn(false).when(secondActivity).occludesParent();
- homeRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ homeRootTask.ensureActivitiesVisible(null /* starting */);
assertTrue(firstActivity.shouldBeVisible());
}
@@ -1419,8 +1418,7 @@
// Any common path that updates activity visibility should clear the unknown visibility
// records that are no longer visible according to hierarchy.
- task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ task.ensureActivitiesVisible(null /* starting */);
// Assume the top activity relayouted, just remove it directly.
unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
// All unresolved records should be removed.
@@ -1441,8 +1439,7 @@
doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
anyBoolean());
- task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ task.ensureActivitiesVisible(null /* starting */);
verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
anyBoolean());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c3102e0..5518c60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -947,7 +947,7 @@
// Recompute the natural configuration in the new display.
mActivity.clearSizeCompatMode();
- mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ mActivity.ensureActivityConfiguration();
// Because the display cannot rotate, the portrait activity will fit the short side of
// display with keeping portrait bounds [200, 0 - 700, 1000] in center.
assertEquals(newDisplayBounds.height(), currentBounds.height());
@@ -4858,7 +4858,7 @@
}
// Make sure to use the provided configuration to construct the size compat fields.
activity.clearSizeCompatMode();
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ activity.ensureActivityConfiguration();
// Make sure the display configuration reflects the change of activity.
if (activity.mDisplayContent.updateOrientation()) {
activity.mDisplayContent.sendNewConfiguration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 8cd9ff3..90493d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -375,8 +375,7 @@
// Always keep things awake.
doReturn(true).when(mWmService.mRoot).hasAwakeDisplay();
// Called when moving activity to pinned stack.
- doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(),
- anyInt(), anyBoolean(), anyBoolean());
+ doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(), anyBoolean());
spyOn(mWmService.mDisplayWindowSettings);
spyOn(mWmService.mDisplayWindowSettingsProvider);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index ec068be..00e22fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -269,8 +269,7 @@
mTaskFragment.getTask().addChild(activityBelow, 0);
// Ensure the activity below is visible
- mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */);
assertEquals(true, activityBelow.isVisibleRequested());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index da7612b..45e1e95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -339,16 +339,14 @@
// Check visibility of occluded tasks
doReturn(false).when(leafTask1).shouldBeVisible(any());
doReturn(true).when(leafTask2).shouldBeVisible(any());
- rootTask.ensureActivitiesVisible(
- null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
assertFalse(activity1.isVisible());
assertTrue(activity2.isVisible());
// Check visibility of not occluded tasks
doReturn(true).when(leafTask1).shouldBeVisible(any());
doReturn(true).when(leafTask2).shouldBeVisible(any());
- rootTask.ensureActivitiesVisible(
- null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
assertTrue(activity1.isVisible());
assertTrue(activity2.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index bd111ad..52e2d8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -89,8 +89,8 @@
}
@Override
- public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags) {
+ public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags) {
return 0;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 71447e7..fddd771 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1495,8 +1495,7 @@
verify(taskSnapshotController, times(0)).recordSnapshot(eq(task1));
enteringAnimReports.clear();
- doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
- anyInt(), anyBoolean(), anyBoolean());
+ doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean());
final boolean[] wasInFinishingTransition = { false };
controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e31ee11..400e4b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -38,7 +38,6 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -306,10 +305,12 @@
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
+ doNothing().when(mClientLifecycleManager).scheduleTransactionItemNow(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
+ mWpc.mWindowSession = getTestSession();
+ mWpc.mWindowSession.onWindowAdded(mock(WindowState.class));
// Non-cached state will send the change directly.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
clearInvocations(mClientLifecycleManager);
@@ -322,13 +323,13 @@
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
- verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransactionItemNow(eq(thread), any());
// Cached -> non-cached will send the previous deferred config immediately.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
final ArgumentCaptor<ConfigurationChangeItem> captor =
ArgumentCaptor.forClass(ConfigurationChangeItem.class);
- verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+ verify(mClientLifecycleManager).scheduleTransactionItemNow(
eq(thread), captor.capture());
final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
captor.getValue().preExecute(client);
@@ -432,7 +433,7 @@
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
GRAMMATICAL_GENDER_NOT_SPECIFIED);
- verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
+ verify(activity).ensureActivityConfiguration();
}
@Test
@@ -443,7 +444,7 @@
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
GRAMMATICAL_GENDER_NOT_SPECIFIED);
verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
- verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
+ verify(activity, never()).ensureActivityConfiguration();
}
@Test
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 1dc5dcf..3a0a6ab 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -35,4 +35,7 @@
"android.hardware.usb-V1.3-java",
"android.hardware.usb-V3-java",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 22a5cd7..9e292be 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2115,6 +2115,20 @@
public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
"com.android.omadm.service.CONFIGURATION_UPDATE";
+ /**
+ * Activity action: Show setting to reset mobile networks.
+ *
+ * <p>On devices with a settings activity to reset mobile networks, the activity should be
+ * launched without additional permissions.
+ *
+ * <p>On some devices, this settings activity may not exist. Callers should ensure that this
+ * case is appropriately handled.
+ */
+ @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS)
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS =
+ "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
+
//
//
// Device Info
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208d..5cbb1aa 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@
.autoFix()
.build()
- return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+ return LintFix.create()
+ .name(annotateFix.getDisplayName())
+ .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
}
private val annotation: String