Merge "Disable failing shell transition tests"
diff --git a/core/api/current.txt b/core/api/current.txt
index ee44198..08adbcb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18528,6 +18528,7 @@
method @NonNull public android.hardware.camera2.CameraExtensionCharacteristics getCameraExtensionCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
+ method public int getTorchStrengthLevel(@NonNull String) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
@@ -18536,6 +18537,7 @@
method public void registerTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback, @Nullable android.os.Handler);
method public void registerTorchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraManager.TorchCallback);
method public void setTorchMode(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
+ method public void turnOnTorchWithStrengthLevel(@NonNull String, int) throws android.hardware.camera2.CameraAccessException;
method public void unregisterAvailabilityCallback(@NonNull android.hardware.camera2.CameraManager.AvailabilityCallback);
method public void unregisterTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback);
}
@@ -18553,6 +18555,7 @@
ctor public CameraManager.TorchCallback();
method public void onTorchModeChanged(@NonNull String, boolean);
method public void onTorchModeUnavailable(@NonNull String);
+ method public void onTorchStrengthLevelChanged(@NonNull String, int);
}
public abstract class CameraMetadata<TKey> {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5a6ea89..a33d0a2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6450,6 +6450,7 @@
method public int flush();
method public long read(long);
method public long read(@NonNull byte[], long, long);
+ method public long seek(long);
method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
method public int start();
method public int stop();
@@ -6585,6 +6586,7 @@
public class DownloadEvent extends android.media.tv.tuner.filter.FilterEvent {
method public int getDataLength();
+ method public int getDownloadId();
method public int getItemFragmentIndex();
method public int getItemId();
method public int getLastItemFragmentIndex();
@@ -6594,11 +6596,13 @@
public class DownloadSettings extends android.media.tv.tuner.filter.Settings {
method @NonNull public static android.media.tv.tuner.filter.DownloadSettings.Builder builder(int);
method public int getDownloadId();
+ method public boolean useDownloadId();
}
public static class DownloadSettings.Builder {
method @NonNull public android.media.tv.tuner.filter.DownloadSettings build();
method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setDownloadId(int);
+ method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setUseDownloadId(boolean);
}
public class Filter implements java.lang.AutoCloseable {
@@ -6697,12 +6701,14 @@
method public long getAudioHandle();
method public long getAvDataId();
method public long getDataLength();
+ method public long getDts();
method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
method @IntRange(from=0) public int getMpuSequenceNumber();
method public long getOffset();
method public long getPts();
method public int getStreamId();
+ method public boolean isDtsPresent();
method public boolean isPrivateData();
method public boolean isPtsPresent();
method public boolean isSecureMemory();
@@ -6812,7 +6818,8 @@
}
public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
- method public int getDataLength();
+ method @Deprecated public int getDataLength();
+ method public long getDataLengthLong();
method public int getSectionNumber();
method public int getTableId();
method public int getVersion();
@@ -7544,6 +7551,7 @@
method public int getSignalStrength();
method public int getSnr();
method public int getSpectralInversion();
+ method @NonNull public int[] getStreamIdList();
method public int getSymbolRate();
method @IntRange(from=0, to=65535) public int getSystemId();
method public int getTransmissionMode();
@@ -7590,6 +7598,7 @@
field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6
field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1
field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa
+ field public static final int FRONTEND_STATUS_TYPE_STREAM_ID_LIST = 39; // 0x27
field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7
field public static final int FRONTEND_STATUS_TYPE_T2_SYSTEM_ID = 29; // 0x1d
field public static final int FRONTEND_STATUS_TYPE_TRANSMISSION_MODE = 27; // 0x1b
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a921500..fd11567 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -353,6 +353,10 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
}
+ public static final class StatusBarManager.DisableInfo {
+ method public boolean isRotationSuggestionDisabled();
+ }
+
public final class SyncNotedAppOp implements android.os.Parcelable {
ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 5750484..77af474 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1352,7 +1352,9 @@
Application app = null;
- String appClass = mApplicationInfo.className;
+ final String myProcessName = Process.myProcessName();
+ String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
+ myProcessName);
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 2392c9a..ae578f5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -165,7 +165,7 @@
*
* @hide
*/
- public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_ROTATE_SUGGESTIONS;
+ public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_NONE;
/**
* disable flags to be applied when the device is sim-locked.
@@ -712,6 +712,7 @@
private boolean mSystemIcons;
private boolean mClock;
private boolean mNotificationIcons;
+ private boolean mRotationSuggestion;
/** @hide */
public DisableInfo(int flags1, int flags2) {
@@ -723,6 +724,7 @@
mSystemIcons = (flags1 & DISABLE_SYSTEM_INFO) != 0;
mClock = (flags1 & DISABLE_CLOCK) != 0;
mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0;
+ mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0;
}
/** @hide */
@@ -846,14 +848,24 @@
}
/**
- * @return {@code true} if no components are disabled (default state)
+ * Returns whether the rotation suggestion is disabled.
*
* @hide
*/
+ @TestApi
+ public boolean isRotationSuggestionDisabled() {
+ return mRotationSuggestion;
+ }
+
+ /**
+ * @return {@code true} if no components are disabled (default state)
+ * @hide
+ */
@SystemApi
public boolean areAllComponentsEnabled() {
return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents
- && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons;
+ && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
+ && !mRotationSuggestion;
}
/** @hide */
@@ -866,6 +878,7 @@
mSystemIcons = false;
mClock = false;
mNotificationIcons = false;
+ mRotationSuggestion = false;
}
/**
@@ -875,7 +888,8 @@
*/
public boolean areAllComponentsDisabled() {
return mStatusBarExpansion && mNavigateHome && mNotificationPeeking
- && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons;
+ && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons
+ && mRotationSuggestion;
}
/** @hide */
@@ -888,6 +902,7 @@
mSystemIcons = true;
mClock = true;
mNotificationIcons = true;
+ mRotationSuggestion = true;
}
@NonNull
@@ -904,6 +919,7 @@
sb.append(" mSystemIcons=").append(mSystemIcons ? "disabled" : "enabled");
sb.append(" mClock=").append(mClock ? "disabled" : "enabled");
sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled");
+ sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled");
return sb.toString();
@@ -927,6 +943,7 @@
if (mSystemIcons) disable1 |= DISABLE_SYSTEM_INFO;
if (mClock) disable1 |= DISABLE_CLOCK;
if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS;
+ if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS;
return new Pair<Integer, Integer>(disable1, disable2);
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 6e1f8b5..18a59d8 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -20,12 +20,15 @@
import static com.android.internal.util.CollectionUtils.emptyIfNull;
+import static java.util.Objects.requireNonNull;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -52,12 +55,11 @@
* device to be shown instead of a list to choose from
*/
@DataClass(
+ genConstructor = false,
genToString = true,
genEqualsHashCode = true,
genHiddenGetters = true,
genParcelable = true,
- genHiddenConstructor = true,
- genBuilder = false,
genConstDefs = false)
public final class AssociationRequest implements Parcelable {
/**
@@ -151,40 +153,76 @@
private final boolean mForceConfirmation;
/**
- * The app package making the request.
- *
+ * The app package name of the application the association will belong to.
* Populated by the system.
- *
* @hide
*/
- private @Nullable String mCallingPackage;
+ private @Nullable String mPackageName;
+
+ /**
+ * The UserId of the user the association will belong to.
+ * Populated by the system.
+ * @hide
+ */
+ private @UserIdInt int mUserId;
/**
* The user-readable description of the device profile's privileges.
- *
* Populated by the system.
- *
* @hide
*/
private @Nullable String mDeviceProfilePrivilegesDescription;
/**
* The time at which his request was created
- *
* @hide
*/
- private long mCreationTime;
+ private final long mCreationTime;
/**
* Whether the user-prompt may be skipped once the device is found.
- *
* Populated by the system.
- *
* @hide
*/
private boolean mSkipPrompt;
/**
+ * Creates a new AssociationRequest.
+ *
+ * @param singleDevice
+ * Whether only a single device should match the provided filter.
+ *
+ * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
+ * address, bonded devices are also searched among. This allows to obtain the necessary app
+ * privileges even if the device is already paired.
+ * @param deviceFilters
+ * If set, only devices matching either of the given filters will be shown to the user
+ * @param deviceProfile
+ * Profile of the device.
+ * @param displayName
+ * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+ * "self-managed" association.
+ * @param selfManaged
+ * Whether the association is to be managed by the companion application.
+ */
+ private AssociationRequest(
+ boolean singleDevice,
+ @NonNull List<DeviceFilter<?>> deviceFilters,
+ @Nullable @DeviceProfile String deviceProfile,
+ @Nullable CharSequence displayName,
+ boolean selfManaged,
+ boolean forceConfirmation) {
+ mSingleDevice = singleDevice;
+ mDeviceFilters = requireNonNull(deviceFilters);
+ mDeviceProfile = deviceProfile;
+ mDisplayName = displayName;
+ mSelfManaged = selfManaged;
+ mForceConfirmation = forceConfirmation;
+
+ mCreationTime = System.currentTimeMillis();
+ }
+
+ /**
* @return profile of the companion device.
*/
public @Nullable @DeviceProfile String getDeviceProfile() {
@@ -237,8 +275,13 @@
}
/** @hide */
- public void setCallingPackage(@NonNull String pkg) {
- mCallingPackage = pkg;
+ public void setPackageName(@NonNull String packageName) {
+ mPackageName = packageName;
+ }
+
+ /** @hide */
+ public void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
}
/** @hide */
@@ -248,7 +291,7 @@
/** @hide */
public void setSkipPrompt(boolean value) {
- mSkipPrompt = true;
+ mSkipPrompt = value;
}
/** @hide */
@@ -258,10 +301,6 @@
return mDeviceFilters;
}
- private void onConstructed() {
- mCreationTime = System.currentTimeMillis();
- }
-
/**
* A builder for {@link AssociationRequest}
*/
@@ -325,7 +364,7 @@
@NonNull
public Builder setDisplayName(@NonNull CharSequence displayName) {
checkNotUsed();
- mDisplayName = Objects.requireNonNull(displayName);
+ mDisplayName = requireNonNull(displayName);
return this;
}
@@ -372,15 +411,13 @@
+ "provide the display name of the device");
}
return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation,
- null, null, -1L, false);
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation);
}
}
-
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -395,88 +432,29 @@
/**
- * Creates a new AssociationRequest.
- *
- * @param singleDevice
- * Whether only a single device should match the provided filter.
- *
- * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
- * address, bonded devices are also searched among. This allows to obtain the necessary app
- * privileges even if the device is already paired.
- * @param deviceFilters
- * If set, only devices matching either of the given filters will be shown to the user
- * @param deviceProfile
- * Profile of the device.
- * @param displayName
- * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
- * "self-managed" association.
- * @param selfManaged
- * Whether the association is to be managed by the companion application.
- * @param forceConfirmation
- * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit
- * confirmation from the user before creating an association, even if such confirmation is not
- * required.
- * @param callingPackage
- * The app package making the request.
- *
- * Populated by the system.
- * @param deviceProfilePrivilegesDescription
- * The user-readable description of the device profile's privileges.
- *
- * Populated by the system.
- * @param creationTime
- * The time at which his request was created
- * @param skipPrompt
- * Whether the user-prompt may be skipped once the device is found.
- *
- * Populated by the system.
- * @hide
- */
- @DataClass.Generated.Member
- public AssociationRequest(
- boolean singleDevice,
- @NonNull List<DeviceFilter<?>> deviceFilters,
- @Nullable @DeviceProfile String deviceProfile,
- @Nullable CharSequence displayName,
- boolean selfManaged,
- boolean forceConfirmation,
- @Nullable String callingPackage,
- @Nullable String deviceProfilePrivilegesDescription,
- long creationTime,
- boolean skipPrompt) {
- this.mSingleDevice = singleDevice;
- this.mDeviceFilters = deviceFilters;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mDeviceFilters);
- this.mDeviceProfile = deviceProfile;
- com.android.internal.util.AnnotationValidations.validate(
- DeviceProfile.class, null, mDeviceProfile);
- this.mDisplayName = displayName;
- this.mSelfManaged = selfManaged;
- this.mForceConfirmation = forceConfirmation;
- this.mCallingPackage = callingPackage;
- this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
- this.mCreationTime = creationTime;
- this.mSkipPrompt = skipPrompt;
-
- onConstructed();
- }
-
- /**
- * The app package making the request.
- *
+ * The app package name of the application the association will belong to.
* Populated by the system.
*
* @hide
*/
@DataClass.Generated.Member
- public @Nullable String getCallingPackage() {
- return mCallingPackage;
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The UserId of the user the association will belong to.
+ * Populated by the system.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @UserIdInt int getUserId() {
+ return mUserId;
}
/**
* The user-readable description of the device profile's privileges.
- *
* Populated by the system.
*
* @hide
@@ -498,7 +476,6 @@
/**
* Whether the user-prompt may be skipped once the device is found.
- *
* Populated by the system.
*
* @hide
@@ -521,7 +498,8 @@
"displayName = " + mDisplayName + ", " +
"selfManaged = " + mSelfManaged + ", " +
"forceConfirmation = " + mForceConfirmation + ", " +
- "callingPackage = " + mCallingPackage + ", " +
+ "packageName = " + mPackageName + ", " +
+ "userId = " + mUserId + ", " +
"deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " +
"creationTime = " + mCreationTime + ", " +
"skipPrompt = " + mSkipPrompt +
@@ -547,7 +525,8 @@
&& Objects.equals(mDisplayName, that.mDisplayName)
&& mSelfManaged == that.mSelfManaged
&& mForceConfirmation == that.mForceConfirmation
- && Objects.equals(mCallingPackage, that.mCallingPackage)
+ && Objects.equals(mPackageName, that.mPackageName)
+ && mUserId == that.mUserId
&& Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription)
&& mCreationTime == that.mCreationTime
&& mSkipPrompt == that.mSkipPrompt;
@@ -566,7 +545,8 @@
_hash = 31 * _hash + Objects.hashCode(mDisplayName);
_hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
_hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
- _hash = 31 * _hash + Objects.hashCode(mCallingPackage);
+ _hash = 31 * _hash + Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + mUserId;
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
_hash = 31 * _hash + Long.hashCode(mCreationTime);
_hash = 31 * _hash + Boolean.hashCode(mSkipPrompt);
@@ -583,16 +563,17 @@
if (mSingleDevice) flg |= 0x1;
if (mSelfManaged) flg |= 0x10;
if (mForceConfirmation) flg |= 0x20;
- if (mSkipPrompt) flg |= 0x200;
+ if (mSkipPrompt) flg |= 0x400;
if (mDeviceProfile != null) flg |= 0x4;
if (mDisplayName != null) flg |= 0x8;
- if (mCallingPackage != null) flg |= 0x40;
- if (mDeviceProfilePrivilegesDescription != null) flg |= 0x80;
+ if (mPackageName != null) flg |= 0x40;
+ if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
if (mDisplayName != null) dest.writeCharSequence(mDisplayName);
- if (mCallingPackage != null) dest.writeString(mCallingPackage);
+ if (mPackageName != null) dest.writeString(mPackageName);
+ dest.writeInt(mUserId);
if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription);
dest.writeLong(mCreationTime);
}
@@ -612,13 +593,14 @@
boolean singleDevice = (flg & 0x1) != 0;
boolean selfManaged = (flg & 0x10) != 0;
boolean forceConfirmation = (flg & 0x20) != 0;
- boolean skipPrompt = (flg & 0x200) != 0;
+ boolean skipPrompt = (flg & 0x400) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader());
String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
- String callingPackage = (flg & 0x40) == 0 ? null : in.readString();
- String deviceProfilePrivilegesDescription = (flg & 0x80) == 0 ? null : in.readString();
+ String packageName = (flg & 0x40) == 0 ? null : in.readString();
+ int userId = in.readInt();
+ String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString();
long creationTime = in.readLong();
this.mSingleDevice = singleDevice;
@@ -631,12 +613,15 @@
this.mDisplayName = displayName;
this.mSelfManaged = selfManaged;
this.mForceConfirmation = forceConfirmation;
- this.mCallingPackage = callingPackage;
+ this.mPackageName = packageName;
+ this.mUserId = userId;
+ com.android.internal.util.AnnotationValidations.validate(
+ UserIdInt.class, null, mUserId);
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
this.mCreationTime = creationTime;
this.mSkipPrompt = skipPrompt;
- onConstructed();
+ // onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
@@ -654,10 +639,10 @@
};
@DataClass.Generated(
- time = 1638368698639L,
+ time = 1638962248060L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nprivate void onConstructed()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)")
+ inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2998f76..76b4e5c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -33,6 +33,7 @@
import android.os.Parcelable;
import android.os.UserHandle;
import android.os.storage.StorageManager;
+import android.util.ArrayMap;
import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -1533,6 +1534,15 @@
private int mHiddenApiPolicy = HIDDEN_API_ENFORCEMENT_DEFAULT;
+ /**
+ * A map from a process name to an (custom) application class name in this package, derived
+ * from the <processes> tag in the app's manifest. This map may not contain all the process
+ * names. Processses not in this map will use the default app class name,
+ * which is {@link #className}, or the default class {@link android.app.Application}.
+ */
+ @Nullable
+ private ArrayMap<String, String> mAppClassNamesByProcess;
+
public void dump(Printer pw, String prefix) {
dump(pw, prefix, DUMP_FLAG_ALL);
}
@@ -1540,8 +1550,14 @@
/** @hide */
public void dump(Printer pw, String prefix, int dumpFlags) {
super.dumpFront(pw, prefix);
- if ((dumpFlags & DUMP_FLAG_DETAILS) != 0 && className != null) {
- pw.println(prefix + "className=" + className);
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ if (className != null) {
+ pw.println(prefix + "className=" + className);
+ }
+ for (int i = 0; i < ArrayUtils.size(mAppClassNamesByProcess); i++) {
+ pw.println(prefix + " process=" + mAppClassNamesByProcess.keyAt(i)
+ + " className=" + mAppClassNamesByProcess.valueAt(i));
+ }
}
if (permission != null) {
pw.println(prefix + "permission=" + permission);
@@ -1967,6 +1983,16 @@
dest.writeInt(nativeHeapZeroInitialized);
sForBoolean.parcel(requestRawExternalStorageAccess, dest, parcelableFlags);
dest.writeLong(createTimestamp);
+ if (mAppClassNamesByProcess == null) {
+ dest.writeInt(0);
+ } else {
+ final int size = mAppClassNamesByProcess.size();
+ dest.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ dest.writeString(mAppClassNamesByProcess.keyAt(i));
+ dest.writeString(mAppClassNamesByProcess.valueAt(i));
+ }
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -2055,6 +2081,13 @@
nativeHeapZeroInitialized = source.readInt();
requestRawExternalStorageAccess = sForBoolean.unparcel(source);
createTimestamp = source.readLong();
+ final int allClassesSize = source.readInt();
+ if (allClassesSize > 0) {
+ mAppClassNamesByProcess = new ArrayMap<>(allClassesSize);
+ for (int i = 0; i < allClassesSize; i++) {
+ mAppClassNamesByProcess.put(source.readString(), source.readString());
+ }
+ }
}
/**
@@ -2538,6 +2571,19 @@
requestRawExternalStorageAccess = value;
}
+ /**
+ * Replaces {@link #mAppClassNamesByProcess}. This takes over the ownership of the passed map.
+ * Do not modify the argument at the callsite.
+ * {@hide}
+ */
+ public void setAppClassNamesByProcess(@Nullable ArrayMap<String, String> value) {
+ if (ArrayUtils.size(value) == 0) {
+ mAppClassNamesByProcess = null;
+ } else {
+ mAppClassNamesByProcess = value;
+ }
+ }
+
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public String getCodePath() { return scanSourceDir; }
@@ -2568,4 +2614,21 @@
public int getNativeHeapZeroInitialized() {
return nativeHeapZeroInitialized;
}
+
+ /**
+ * Return the application class name defined in the manifest. The class name set in the
+ * <processes> tag for this process, then return it. Otherwise it'll return the class
+ * name set in the <application> tag. If neither is set, it'll return null.
+ * @hide
+ */
+ @Nullable
+ public String getCustomApplicationClassNameForProcess(String processName) {
+ if (mAppClassNamesByProcess != null) {
+ String byProcess = mAppClassNamesByProcess.get(processName);
+ if (byProcess != null) {
+ return byProcess;
+ }
+ }
+ return className;
+ }
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 2fa5df7..fc9f1a5 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -118,6 +118,7 @@
ParsingPackage addQueriesProvider(String authority);
+ /** Sets a process name -> {@link ParsedProcess} map coming from the <processes> tag. */
ParsingPackage setProcesses(@NonNull Map<String, ParsedProcess> processes);
ParsingPackage asSplit(
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index f03ab6a..424f477 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -62,6 +62,7 @@
import android.os.Parcelable;
import android.os.storage.StorageManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.SparseArray;
@@ -297,6 +298,9 @@
// @DataClass.ParcelWith(ParsingUtils.StringPairListParceler.class)
private List<Pair<String, ParsedIntentInfo>> preferredActivityFilters = emptyList();
+ /**
+ * Map from a process name to a {@link ParsedProcess}.
+ */
@NonNull
private Map<String, ParsedProcess> processes = emptyMap();
@@ -1131,10 +1135,46 @@
appInfo.setSplitCodePaths(splitCodePaths);
appInfo.setSplitResourcePaths(splitCodePaths);
appInfo.setVersionCode(mLongVersionCode);
+ appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
return appInfo;
}
+ /**
+ * Create a map from a process name to the custom application class for this process,
+ * which comes from <processes><process android:name="xxx">.
+ *
+ * The original information is stored in {@link #processes}, but it's stored in
+ * a form of: [process name] -[1:N]-> [package name] -[1:N]-> [class name].
+ * We scan it and collect the process names and their app class names, only for this package.
+ *
+ * The resulting map only contains processes with a custom application class set.
+ */
+ @Nullable
+ private ArrayMap<String, String> buildAppClassNamesByProcess() {
+ if (processes == null) {
+ return null;
+ }
+ final ArrayMap<String, String> ret = new ArrayMap<>(4);
+ for (String processName : processes.keySet()) {
+ final ParsedProcess process = processes.get(processName);
+ final ArrayMap<String, String> appClassesByPackage =
+ process.getAppClassNamesByPackage();
+
+ for (int i = 0; i < appClassesByPackage.size(); i++) {
+ final String packageName = appClassesByPackage.keyAt(i);
+
+ if (this.packageName.equals(packageName)) {
+ final String appClassName = appClassesByPackage.valueAt(i);
+ if (!TextUtils.isEmpty(appClassName)) {
+ ret.put(processName, appClassName);
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index c2d5163..27a540d 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
import android.os.Parcelable;
+import android.util.ArrayMap;
import java.util.Set;
@@ -37,6 +38,15 @@
@NonNull
String getName();
+ /**
+ * The app class names in this (potentially shared) process, from a package name to
+ * the application class name.
+ * It's a map, because in shared processes, different packages can have different application
+ * classes.
+ */
+ @NonNull
+ ArrayMap<String, String> getAppClassNamesByPackage();
+
@ApplicationInfo.NativeHeapZeroInitialized
int getNativeHeapZeroInitialized();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
index 3fd60eb..d404ecfd 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
@@ -22,6 +22,7 @@
import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
@@ -39,6 +40,11 @@
@NonNull
private String name;
+
+ /** @see ParsedProcess#getAppClassNamesByPackage() */
+ @NonNull
+ private ArrayMap<String, String> appClassNamesByPackage = ArrayMap.EMPTY;
+
@NonNull
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
private Set<String> deniedPermissions = emptySet();
@@ -55,6 +61,8 @@
public ParsedProcessImpl(@NonNull ParsedProcess other) {
name = other.getName();
+ appClassNamesByPackage = (other.getAppClassNamesByPackage().size() == 0)
+ ? ArrayMap.EMPTY : new ArrayMap<>(other.getAppClassNamesByPackage());
deniedPermissions = new ArraySet<>(other.getDeniedPermissions());
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
@@ -66,6 +74,21 @@
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+
+ final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
+ for (int i = 0; i < oacn.size(); i++) {
+ appClassNamesByPackage.put(oacn.keyAt(i), oacn.valueAt(i));
+ }
+ }
+
+ /**
+ * Sets a custom application name used in this process for a given package.
+ */
+ public void putAppClassNameForPackage(String packageName, String className) {
+ if (appClassNamesByPackage.size() == 0) {
+ appClassNamesByPackage = new ArrayMap<>(4);
+ }
+ appClassNamesByPackage.put(packageName, className);
}
@@ -83,9 +106,14 @@
//@formatter:off
+ /**
+ * Creates a new ParsedProcessImpl.
+ *
+ */
@DataClass.Generated.Member
public ParsedProcessImpl(
@NonNull String name,
+ @NonNull ArrayMap<String,String> appClassNamesByPackage,
@NonNull Set<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
@@ -93,6 +121,9 @@
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
+ this.appClassNamesByPackage = appClassNamesByPackage;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, appClassNamesByPackage);
this.deniedPermissions = deniedPermissions;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deniedPermissions);
@@ -114,6 +145,14 @@
return name;
}
+ /**
+ * @see ParsedProcess#getAppClassNamesByPackage()
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArrayMap<String,String> getAppClassNamesByPackage() {
+ return appClassNamesByPackage;
+ }
+
@DataClass.Generated.Member
public @NonNull Set<String> getDeniedPermissions() {
return deniedPermissions;
@@ -142,6 +181,17 @@
return this;
}
+ /**
+ * @see ParsedProcess#getAppClassNamesByPackage()
+ */
+ @DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setAppClassNamesByPackage(@NonNull ArrayMap<String,String> value) {
+ appClassNamesByPackage = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, appClassNamesByPackage);
+ return this;
+ }
+
@DataClass.Generated.Member
public @NonNull ParsedProcessImpl setDeniedPermissions(@NonNull Set<String> value) {
deniedPermissions = value;
@@ -192,6 +242,7 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeString(name);
+ dest.writeMap(appClassNamesByPackage);
sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
dest.writeInt(gwpAsanMode);
dest.writeInt(memtagMode);
@@ -210,6 +261,8 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
String _name = in.readString();
+ ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
+ in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
int _memtagMode = in.readInt();
@@ -218,6 +271,9 @@
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
+ this.appClassNamesByPackage = _appClassNamesByPackage;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, appClassNamesByPackage);
this.deniedPermissions = _deniedPermissions;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deniedPermissions);
@@ -249,10 +305,10 @@
};
@DataClass.Generated(
- time = 1627605368434L,
+ time = 1639076603310L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
index cf83586..5e4cf66 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -97,7 +97,12 @@
return input.error(processNameResult);
}
+ String packageName = pkg.getPackageName();
+ String className = ParsingUtils.buildClassName(packageName,
+ sa.getNonConfigurationString(R.styleable.AndroidManifestProcess_name, 0));
+
proc.setName(processNameResult.getResult());
+ proc.putAppClassNameForPackage(packageName, className);
proc.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1));
proc.setMemtagMode(sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1));
if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized)) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 93f1d61..c12e819 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1036,6 +1036,95 @@
}
/**
+ * Set the brightness level of the flashlight associated with the given cameraId in torch
+ * mode. If the torch is OFF and torchStrength is >= 1, torch will turn ON with the
+ * strength level specified in torchStrength.
+ *
+ * <p>Use
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}
+ * to check whether the camera device supports flash unit strength control or not. If this value
+ * is greater than 1, applications can call this API to control the flashlight brightness level.
+ * </p>
+ *
+ * <p>If {@link #turnOnTorchWithStrengthLevel} is called to change the brightness level of the
+ * flash unit {@link CameraManager.TorchCallback#onTorchStrengthLevelChanged} will be invoked.
+ * If the new desired strength level is same as previously set level, then this callback will
+ * not be invoked.
+ * If the torch is OFF and {@link #turnOnTorchWithStrengthLevel} is called with level >= 1,
+ * the torch will be turned ON with that brightness level. In this case
+ * {@link CameraManager.TorchCallback#onTorchModeChanged} will also be invoked.
+ * </p>
+ *
+ * <p>When the torch is turned OFF via {@link #setTorchMode}, the flashlight brightness level
+ * will reset to default value
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}
+ * In this case the {@link CameraManager.TorchCallback#onTorchStrengthLevelChanged} will not be
+ * invoked.
+ * </p>
+ *
+ * <p>If torch is enabled via {@link #setTorchMode} after calling
+ * {@link #turnOnTorchWithStrengthLevel} with level N then the flash unit will have the
+ * brightness level N.
+ * Since multiple applications are free to call {@link #setTorchMode}, when the latest
+ * application that turned ON the torch mode exits, the torch mode will be turned OFF
+ * and in this case the brightness level will reset to default level.
+ * </p>
+ *
+ * @param cameraId
+ * The unique identifier of the camera device that the flash unit belongs to.
+ * @param torchStrength
+ * The desired brightness level to be set for the flash unit in the range 1 to
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}.
+ *
+ * @throws CameraAccessException if it failed to access the flash unit.
+ * {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
+ * is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
+ * other camera resources needed to turn on the torch mode are in use.
+ * {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera
+ * service is not available.
+ * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
+ * or previously available camera device, the camera device doesn't have a
+ * flash unit or if torchStrength is not within the range i.e. is greater than
+ * the maximum level
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}
+ * or <= 0.
+ *
+ */
+ public void turnOnTorchWithStrengthLevel(@NonNull String cameraId, int torchStrength)
+ throws CameraAccessException {
+ if (CameraManagerGlobal.sCameraServiceDisabled) {
+ throw new IllegalArgumentException("No camera available on device");
+ }
+ CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength);
+ }
+
+ /**
+ * Returns the brightness level of the flash unit associated with the cameraId.
+ *
+ * @param cameraId
+ * The unique identifier of the camera device that the flash unit belongs to.
+ * @return The brightness level of the flash unit associated with cameraId.
+ * When the torch is turned OFF, the strength level will reset to a default level
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}.
+ * In this case the return value will be
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}
+ * rather than 0.
+ *
+ * @throws CameraAccessException if it failed to access the flash unit.
+ * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
+ * or previously available camera device, or the camera device doesn't have a
+ * flash unit.
+ *
+ */
+ public int getTorchStrengthLevel(@NonNull String cameraId)
+ throws CameraAccessException {
+ if (CameraManagerGlobal.sCameraServiceDisabled) {
+ throw new IllegalArgumentException("No camera available on device.");
+ }
+ return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
+ }
+
+ /**
* A callback for camera devices becoming available or unavailable to open.
*
* <p>Cameras become available when they are no longer in use, or when a new
@@ -1239,6 +1328,24 @@
public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
// default empty implementation
}
+
+ /**
+ * A camera's flash unit brightness level has been changed in torch mode via
+ * {@link #turnOnTorchWithStrengthLevel}. When the torch is turned OFF, this
+ * callback will not be triggered even though the torch strength level resets to
+ * default value
+ * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param cameraId The unique identifier of the camera whose flash unit brightness level has
+ * been changed.
+ *
+ * @param newStrengthLevel The brightness level of the flash unit that has been changed to.
+ */
+ public void onTorchStrengthLevelChanged(@NonNull String cameraId, int newStrengthLevel) {
+ // default empty implementation
+ }
}
/**
@@ -1642,6 +1749,10 @@
public void onTorchStatusChanged(int status, String id) throws RemoteException {
}
@Override
+ public void onTorchStrengthLevelChanged(String id, int newStrengthLevel)
+ throws RemoteException {
+ }
+ @Override
public void onCameraAccessPrioritiesChanged() {
}
@Override
@@ -1825,6 +1936,57 @@
}
}
+ public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength) throws
+ CameraAccessException {
+ synchronized(mLock) {
+
+ if (cameraId == null) {
+ throw new IllegalArgumentException("cameraId was null");
+ }
+
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+
+ try {
+ cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength,
+ mTorchClientBinder);
+ } catch(ServiceSpecificException e) {
+ throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+ }
+ }
+
+ public int getTorchStrengthLevel(String cameraId) throws CameraAccessException {
+ int torchStrength = 0;
+ synchronized(mLock) {
+ if (cameraId == null) {
+ throw new IllegalArgumentException("cameraId was null");
+ }
+
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+
+ try {
+ torchStrength = cameraService.getTorchStrengthLevel(cameraId);
+ } catch(ServiceSpecificException e) {
+ throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+ }
+ return torchStrength;
+ }
+
private void handleRecoverableSetupErrors(ServiceSpecificException e) {
switch (e.errorCode) {
case ICameraService.ERROR_DISCONNECTED:
@@ -1984,6 +2146,18 @@
}
}
+ private void postSingleTorchStrengthLevelUpdate(final TorchCallback callback,
+ final Executor executor, final String id, final int newStrengthLevel) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ callback.onTorchStrengthLevelChanged(id, newStrengthLevel);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Send the state of all known cameras to the provided listener, to initialize
* the listener's knowledge of camera state.
@@ -2167,6 +2341,22 @@
}
} // onTorchStatusChangedLocked
+ private void onTorchStrengthLevelChangedLocked(String cameraId, int newStrengthLevel) {
+ if (DEBUG) {
+
+ Log.v(TAG,
+ String.format("Camera id %s has torch strength level changed to %d",
+ cameraId, newStrengthLevel));
+ }
+
+ final int callbackCount = mTorchCallbackMap.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final Executor executor = mTorchCallbackMap.valueAt(i);
+ final TorchCallback callback = mTorchCallbackMap.keyAt(i);
+ postSingleTorchStrengthLevelUpdate(callback, executor, cameraId, newStrengthLevel);
+ }
+ } // onTorchStrengthLevelChanged
+
/**
* Register a callback to be notified about camera device availability with the
* global listener singleton.
@@ -2258,6 +2448,14 @@
}
@Override
+ public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel)
+ throws RemoteException {
+ synchronized (mLock) {
+ onTorchStrengthLevelChangedLocked(cameraId, newStrengthLevel);
+ }
+ }
+
+ @Override
public void onCameraAccessPrioritiesChanged() {
synchronized (mLock) {
final int callbackCount = mCallbackMap.size();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0894877..55c34fc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6050,6 +6050,13 @@
<permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to launch device manager setup screens.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"
+ android:protectionLevel="signature|role" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 0a6ef7c..06f347f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2584,6 +2584,9 @@
<declare-styleable name="AndroidManifestProcess" parent="AndroidManifestProcesses">
<!-- Required name of the process that is allowed -->
<attr name="process" />
+ <!-- custom Application class name. We use call it "name", not "className", to be
+ consistent with the Application tag. -->
+ <attr name="name" />
<attr name="gwpAsanMode" />
<attr name="memtagMode" />
<attr name="nativeHeapZeroInitialized" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 81db63e..624940b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -394,6 +394,8 @@
<!-- Permissions required for Incremental CTS tests -->
<permission name="com.android.permission.USE_INSTALLER_V2"/>
<permission name="android.permission.LOADER_USAGE_STATS"/>
+ <!-- Permissions required for Package Verifier tests -->
+ <permission name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<!-- Permission required to test system only camera devices. -->
<permission name="android.permission.SYSTEM_CAMERA" />
<!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index cd635c1..51b7eaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -562,17 +562,10 @@
overflowBubble(reason, bubbleToRemove);
if (mBubbles.size() == 1) {
- if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
- // No more active bubbles but we have stuff in the overflow -- select that view
- // if we're already expanded or always showing.
- setShowingOverflow(true);
- setSelectedBubbleInternal(mOverflow);
- } else {
- setExpandedInternal(false);
- // Don't use setSelectedBubbleInternal because we don't want to trigger an
- // applyUpdate
- mSelectedBubble = null;
- }
+ setExpandedInternal(false);
+ // Don't use setSelectedBubbleInternal because we don't want to trigger an
+ // applyUpdate
+ mSelectedBubble = null;
}
if (indexToRemove < mBubbles.size() - 1) {
// Removing anything but the last bubble means positions will change.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index b65a2e4..8e6c05d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -151,7 +151,13 @@
final Rect rightHitRegion = new Rect();
final Rect rightDrawRegion = bottomOrRightBounds;
- displayRegion.splitVertically(leftHitRegion, rightHitRegion);
+ // If we have existing split regions use those bounds, otherwise split it 50/50
+ if (inSplitScreen) {
+ leftHitRegion.set(topOrLeftBounds);
+ rightHitRegion.set(bottomOrRightBounds);
+ } else {
+ displayRegion.splitVertically(leftHitRegion, rightHitRegion);
+ }
mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
@@ -162,8 +168,13 @@
final Rect bottomHitRegion = new Rect();
final Rect bottomDrawRegion = bottomOrRightBounds;
- displayRegion.splitHorizontally(
- topHitRegion, bottomHitRegion);
+ // If we have existing split regions use those bounds, otherwise split it 50/50
+ if (inSplitScreen) {
+ topHitRegion.set(topOrLeftBounds);
+ bottomHitRegion.set(bottomOrRightBounds);
+ } else {
+ displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
+ }
mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 67f9062..fd3be2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.draganddrop;
import static android.app.StatusBarManager.DISABLE_NONE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import android.animation.Animator;
@@ -32,11 +34,11 @@
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.view.DragEvent;
import android.view.SurfaceControl;
-import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
@@ -73,6 +75,7 @@
private DropZoneView mDropZoneView2;
private int mDisplayMargin;
+ private int mDividerSize;
private Insets mInsets = Insets.NONE;
private boolean mIsShowing;
@@ -89,13 +92,15 @@
mDisplayMargin = context.getResources().getDimensionPixelSize(
R.dimen.drop_layout_display_margin);
+ mDividerSize = context.getResources().getDimensionPixelSize(
+ R.dimen.split_divider_bar_width);
mDropZoneView1 = new DropZoneView(context);
mDropZoneView2 = new DropZoneView(context);
- addView(mDropZoneView1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- addView(mDropZoneView2, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT,
+ MATCH_PARENT));
+ addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT,
+ MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
updateContainerMargins();
@@ -165,44 +170,82 @@
mHasDropped = false;
mCurrentTarget = null;
- List<ActivityManager.RunningTaskInfo> tasks = null;
- // Figure out the splashscreen info for the existing task(s).
- try {
- tasks = ActivityTaskManager.getService().getTasks(2,
- false /* filterOnlyVisibleRecents */,
- false /* keepIntentExtra */);
- } catch (RemoteException e) {
- // don't show an icon / will just use the defaults
- }
- if (tasks != null && !tasks.isEmpty()) {
- ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
- Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
- int bgColor1 = getResizingBackgroundColor(taskInfo1);
-
- boolean alreadyInSplit = mSplitScreenController != null
- && mSplitScreenController.isSplitScreenVisible();
- if (alreadyInSplit && tasks.size() > 1) {
- ActivityManager.RunningTaskInfo taskInfo2 = tasks.get(1);
- Drawable icon2 = mIconProvider.getIcon(taskInfo2.topActivityInfo);
- int bgColor2 = getResizingBackgroundColor(taskInfo2);
-
- // figure out which task is on which side
- int splitPosition1 = mSplitScreenController.getSplitPosition(taskInfo1.taskId);
- boolean isTask1TopOrLeft = splitPosition1 == SPLIT_POSITION_TOP_OR_LEFT;
- if (isTask1TopOrLeft) {
- mDropZoneView1.setAppInfo(bgColor1, icon1);
- mDropZoneView2.setAppInfo(bgColor2, icon2);
- } else {
- mDropZoneView2.setAppInfo(bgColor1, icon1);
- mDropZoneView1.setAppInfo(bgColor2, icon2);
- }
- } else {
+ boolean alreadyInSplit = mSplitScreenController != null
+ && mSplitScreenController.isSplitScreenVisible();
+ if (!alreadyInSplit) {
+ List<ActivityManager.RunningTaskInfo> tasks = null;
+ // Figure out the splashscreen info for the existing task.
+ try {
+ tasks = ActivityTaskManager.getService().getTasks(1,
+ false /* filterOnlyVisibleRecents */,
+ false /* keepIntentExtra */);
+ } catch (RemoteException e) {
+ // don't show an icon / will just use the defaults
+ }
+ if (tasks != null && !tasks.isEmpty()) {
+ ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
+ Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
+ int bgColor1 = getResizingBackgroundColor(taskInfo1);
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
+ updateDropZoneSizes(null, null); // passing null splits the views evenly
}
+ } else {
+ // We're already in split so get taskInfo from the controller to populate icon / color.
+ ActivityManager.RunningTaskInfo topOrLeftTask =
+ mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ ActivityManager.RunningTaskInfo bottomOrRightTask =
+ mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ if (topOrLeftTask != null && bottomOrRightTask != null) {
+ Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
+ int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
+ Drawable bottomOrRightIcon = mIconProvider.getIcon(
+ bottomOrRightTask.topActivityInfo);
+ int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
+ mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
+ mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
+ }
+
+ // Update the dropzones to match existing split sizes
+ Rect topOrLeftBounds = new Rect();
+ Rect bottomOrRightBounds = new Rect();
+ mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
+ updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
}
}
+ /**
+ * Sets the size of the two drop zones based on the provided bounds. The divider sits between
+ * the views and its size is included in the calculations.
+ *
+ * @param bounds1 bounds to apply to the first dropzone view, null if split in half.
+ * @param bounds2 bounds to apply to the second dropzone view, null if split in half.
+ */
+ private void updateDropZoneSizes(Rect bounds1, Rect bounds2) {
+ final int orientation = getResources().getConfiguration().orientation;
+ final boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
+ final int halfDivider = mDividerSize / 2;
+ final LinearLayout.LayoutParams dropZoneView1 =
+ (LayoutParams) mDropZoneView1.getLayoutParams();
+ final LinearLayout.LayoutParams dropZoneView2 =
+ (LayoutParams) mDropZoneView2.getLayoutParams();
+ if (isPortrait) {
+ dropZoneView1.width = MATCH_PARENT;
+ dropZoneView2.width = MATCH_PARENT;
+ dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
+ dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
+ } else {
+ dropZoneView1.width = bounds1 != null ? bounds1.width() + halfDivider : MATCH_PARENT;
+ dropZoneView2.width = bounds2 != null ? bounds2.width() + halfDivider : MATCH_PARENT;
+ dropZoneView1.height = MATCH_PARENT;
+ dropZoneView2.height = MATCH_PARENT;
+ }
+ dropZoneView1.weight = bounds1 != null ? 0 : 1;
+ dropZoneView2.weight = bounds2 != null ? 0 : 1;
+ mDropZoneView1.setLayoutParams(dropZoneView1);
+ mDropZoneView2.setLayoutParams(dropZoneView2);
+ }
+
public void show() {
mIsShowing = true;
recomputeDropTargets();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index cf4e56e..46c4a40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -186,6 +186,15 @@
return mStageCoordinator.isSplitScreenVisible();
}
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
+ if (isSplitScreenVisible()) {
+ int taskId = mStageCoordinator.getTaskId(splitPosition);
+ return mTaskOrganizer.getRunningTaskInfo(taskId);
+ }
+ return null;
+ }
+
public boolean isTaskInSplitScreen(int taskId) {
return isSplitScreenVisible()
&& mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index cdaa54c..dd27017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -522,6 +522,14 @@
return SplitLayout.reversePosition(mSideStagePosition);
}
+ int getTaskId(@SplitPosition int splitPosition) {
+ if (mSideStagePosition == splitPosition) {
+ return mSideStage.getTopVisibleChildTaskId();
+ } else {
+ return mMainStage.getTopVisibleChildTaskId();
+ }
+ }
+
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 8bc1223..185479b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -794,7 +794,7 @@
}
@Test
- public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() {
+ public void test_expanded_removeLastBubble_collapsesIfOverflowNotEmpty() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
changeExpandedStateAtTime(true, 2000);
@@ -804,7 +804,7 @@
mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0);
- assertSelectionChangedTo(mBubbleData.getOverflow());
+ assertExpandedChangedTo(false);
}
@Test
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
index cfd8583..6f3ab03 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -98,6 +98,7 @@
private native void nativeSetFileDescriptor(int fd);
private native long nativeRead(long size);
private native long nativeRead(byte[] bytes, long offset, long size);
+ private native long nativeSeek(long pos);
private DvrPlayback() {
mUserId = Process.myUid();
@@ -243,7 +244,7 @@
*
* @param fd the file descriptor to read data.
* @see #read(long)
- * @see #read(byte[], long, long)
+ * @see #seek(long)
*/
public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
nativeSetFileDescriptor(fd.getFd());
@@ -261,19 +262,30 @@
}
/**
- * Reads data from the buffer for DVR playback and copies to the given byte array.
+ * Reads data from the buffer for DVR playback.
*
- * @param bytes the byte array to store the data.
- * @param offset the index of the first byte in {@code bytes} to copy to.
+ * @param buffer the byte array where DVR reads data from.
+ * @param offset the index of the first byte in {@code buffer} to read.
* @param size the maximum number of bytes to read.
* @return the number of bytes read.
*/
@BytesLong
- public long read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
- if (size + offset > bytes.length) {
+ public long read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+ if (size + offset > buffer.length) {
throw new ArrayIndexOutOfBoundsException(
- "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+ "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size);
}
- return nativeRead(bytes, offset, size);
+ return nativeRead(buffer, offset, size);
+ }
+
+ /**
+ * Sets the file pointer offset of the file descriptor.
+ *
+ * @param pos the offset position, measured in bytes from the beginning of the file.
+ * @return the new offset position.
+ */
+ @BytesLong
+ public long seek(@BytesLong long pos) {
+ return nativeSeek(pos);
}
}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 212a713..e72026a 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -216,7 +216,6 @@
*
* @param fd the file descriptor to write data.
* @see #write(long)
- * @see #write(byte[], long, long)
*/
public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
nativeSetFileDescriptor(fd.getFd());
@@ -236,17 +235,17 @@
/**
* Writes recording data to buffer.
*
- * @param bytes the byte array stores the data to be written to DVR.
- * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+ * @param buffer the byte array stores the data from DVR.
+ * @param offset the index of the first byte in {@code buffer} to write the data from DVR.
* @param size the maximum number of bytes to write.
* @return the number of bytes written.
*/
@BytesLong
- public long write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
- if (size + offset > bytes.length) {
+ public long write(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+ if (size + offset > buffer.length) {
throw new ArrayIndexOutOfBoundsException(
- "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+ "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size);
}
- return nativeWrite(bytes, offset, size);
+ return nativeWrite(buffer, offset, size);
}
}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 394211be..25989db 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -27,15 +27,17 @@
@SystemApi
public class DownloadEvent extends FilterEvent {
private final int mItemId;
+ private final int mDownloadId;
private final int mMpuSequenceNumber;
private final int mItemFragmentIndex;
private final int mLastItemFragmentIndex;
private final int mDataLength;
// This constructor is used by JNI code only
- private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+ private DownloadEvent(int itemId, int downloadId, int mpuSequenceNumber, int itemFragmentIndex,
int lastItemFragmentIndex, int dataLength) {
mItemId = itemId;
+ mDownloadId = downloadId;
mMpuSequenceNumber = mpuSequenceNumber;
mItemFragmentIndex = itemFragmentIndex;
mLastItemFragmentIndex = lastItemFragmentIndex;
@@ -50,6 +52,15 @@
}
/**
+ * Gets download ID.
+ *
+ * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+ * return {@code -1}.
+ * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+ */
+ public int getDownloadId() { return mDownloadId; }
+
+ /**
* Gets MPU sequence number of filtered data.
*/
@IntRange(from = 0)
@@ -80,4 +91,3 @@
return mDataLength;
}
}
-
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 7ba923e..e2cfd7c 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
/**
* Filter Settings for a Download.
@@ -27,10 +28,12 @@
*/
@SystemApi
public class DownloadSettings extends Settings {
+ private final boolean mUseDownloadId;
private final int mDownloadId;
- private DownloadSettings(int mainType, int downloadId) {
+ private DownloadSettings(int mainType, boolean useDownloadId, int downloadId) {
super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_DOWNLOAD));
+ mUseDownloadId = useDownloadId;
mDownloadId = downloadId;
}
@@ -42,6 +45,15 @@
}
/**
+ * Gets whether download ID is used.
+ *
+ * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+ * return {@code false}.
+ * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+ */
+ public boolean useDownloadId() { return mUseDownloadId; }
+
+ /**
* Creates a builder for {@link DownloadSettings}.
*
* @param mainType the filter main type.
@@ -56,6 +68,7 @@
*/
public static class Builder {
private final int mMainType;
+ private boolean mUseDownloadId = false;
private int mDownloadId;
private Builder(int mainType) {
@@ -63,6 +76,24 @@
}
/**
+ * Sets whether download ID is used or not.
+ *
+ * <p>This configuration is only supported in Tuner 2.0 or higher version. Unsupported
+ * version will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the
+ * version information.
+ *
+ * <p>Default value is {@code false}.
+ */
+ @NonNull
+ public Builder setUseDownloadId(boolean useDownloadId) {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0, "setUseDownloadId")) {
+ mUseDownloadId = useDownloadId;
+ }
+ return this;
+ }
+
+ /**
* Sets download ID.
*/
@NonNull
@@ -76,7 +107,7 @@
*/
@NonNull
public DownloadSettings build() {
- return new DownloadSettings(mMainType, mDownloadId);
+ return new DownloadSettings(mMainType, mUseDownloadId, mDownloadId);
}
}
}
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index dbd85e9..79d4062 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -40,6 +40,8 @@
private final int mStreamId;
private final boolean mIsPtsPresent;
private final long mPts;
+ private final boolean mIsDtsPresent;
+ private final long mDts;
private final long mDataLength;
private final long mOffset;
private LinearBlock mLinearBlock;
@@ -50,12 +52,14 @@
private final AudioDescriptor mExtraMetaData;
// This constructor is used by JNI code only
- private MediaEvent(int streamId, boolean isPtsPresent, long pts, long dataLength, long offset,
- LinearBlock buffer, boolean isSecureMemory, long dataId, int mpuSequenceNumber,
- boolean isPrivateData, AudioDescriptor extraMetaData) {
+ private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
+ long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
+ int mpuSequenceNumber, boolean isPrivateData, AudioDescriptor extraMetaData) {
mStreamId = streamId;
mIsPtsPresent = isPtsPresent;
mPts = pts;
+ mIsDtsPresent = isDtsPresent;
+ mDts = dts;
mDataLength = dataLength;
mOffset = offset;
mLinearBlock = buffer;
@@ -90,6 +94,26 @@
}
/**
+ * Returns whether DTS (Decode Time Stamp) is present.
+ *
+ * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+ * return {@code false}.
+ * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+ *
+ * @return {@code true} if DTS is present in PES header; {@code false} otherwise.
+ */
+ public boolean isDtsPresent() { return mIsDtsPresent; }
+
+ /**
+ * Gets DTS (Decode Time Stamp) for audio or video frame.
+ *
+ * * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+ * return {@code -1}.
+ * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+ */
+ public long getDts() { return mDts; }
+
+ /**
* Gets data size in bytes of audio or video frame.
*/
@BytesLong
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
index ff12492..182bb94 100644
--- a/media/java/android/media/tv/tuner/filter/SectionEvent.java
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -28,10 +28,10 @@
private final int mTableId;
private final int mVersion;
private final int mSectionNum;
- private final int mDataLength;
+ private final long mDataLength;
// This constructor is used by JNI code only
- private SectionEvent(int tableId, int version, int sectionNum, int dataLength) {
+ private SectionEvent(int tableId, int version, int sectionNum, long dataLength) {
mTableId = tableId;
mVersion = version;
mSectionNum = sectionNum;
@@ -61,8 +61,13 @@
/**
* Gets data size in bytes of filtered data.
+ *
+ * @deprecated Use {@link #getDataLengthLong()}
*/
+ @Deprecated
public int getDataLength() {
- return mDataLength;
+ return (int) getDataLengthLong();
}
+
+ public long getDataLengthLong() { return mDataLength; }
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 31f1a63..582e4f5 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -19,10 +19,10 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.media.tv.tuner.Lnb;
import android.media.tv.tuner.TunerVersionChecker;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,7 +53,7 @@
FRONTEND_STATUS_TYPE_MODULATIONS_EXT, FRONTEND_STATUS_TYPE_ROLL_OFF,
FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR,
FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE,
- FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG})
+ FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_ID_LIST})
@Retention(RetentionPolicy.SOURCE)
public @interface FrontendStatusType {}
@@ -254,6 +254,12 @@
public static final int FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG =
android.hardware.tv.tuner.FrontendStatusType.ISDBT_PARTIAL_RECEPTION_FLAG;
+ /**
+ * Stream ID list included in a transponder.
+ */
+ public static final int FRONTEND_STATUS_TYPE_STREAM_ID_LIST =
+ android.hardware.tv.tuner.FrontendStatusType.STREAM_ID_LIST;
+
/** @hide */
@IntDef(value = {
AtscFrontendSettings.MODULATION_UNDEFINED,
@@ -493,6 +499,7 @@
private Boolean mIsShortFrames;
private Integer mIsdbtMode;
private Integer mIsdbtPartialReceptionFlag;
+ private int[] mStreamIds;
// Constructed and fields set by JNI code.
private FrontendStatus() {
@@ -1001,6 +1008,24 @@
}
/**
+ * Gets stream id list included in a transponder.
+ *
+ * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL
+ * doesn't return stream id list status will throw IllegalStateException. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @SuppressLint("ArrayReturn")
+ @NonNull
+ public int[] getStreamIdList() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0, "stream id list status");
+ if (mStreamIds == null) {
+ throw new IllegalStateException("stream id list status is empty");
+ }
+ return mStreamIds;
+ }
+
+ /**
* Information of each tuning Physical Layer Pipes.
*/
public static class Atsc3PlpTuningInfo {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c230df3..e91e238 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -588,13 +588,13 @@
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/SectionEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIII)V");
+ jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
const DemuxFilterSectionEvent §ionEvent = event.get<DemuxFilterEvent::Tag::section>();
jint tableId = sectionEvent.tableId;
jint version = sectionEvent.version;
jint sectionNum = sectionEvent.sectionNum;
- jint dataLength = sectionEvent.dataLength;
+ jlong dataLength = sectionEvent.dataLength;
jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
env->SetObjectArrayElement(arr, size, obj);
@@ -604,10 +604,9 @@
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MediaEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz,
- "<init>",
- "(IZJJJLandroid/media/MediaCodec$LinearBlock;"
- "ZJIZLandroid/media/tv/tuner/filter/AudioDescriptor;)V");
+ jmethodID eventInit = env->GetMethodID(eventClazz, "<init>",
+ "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
+ "ZJIZLandroid/media/tv/tuner/filter/AudioDescriptor;)V");
jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
@@ -633,15 +632,17 @@
jint streamId = mediaEvent.streamId;
jboolean isPtsPresent = mediaEvent.isPtsPresent;
jlong pts = mediaEvent.pts;
+ jboolean isDtsPresent = mediaEvent.isDtsPresent;
+ jlong dts = mediaEvent.dts;
jlong offset = mediaEvent.offset;
jboolean isSecureMemory = mediaEvent.isSecureMemory;
jlong avDataId = mediaEvent.avDataId;
jint mpuSequenceNumber = mediaEvent.mpuSequenceNumber;
jboolean isPesPrivateData = mediaEvent.isPesPrivateData;
- jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, dataLength,
- offset, nullptr, isSecureMemory, avDataId, mpuSequenceNumber,
- isPesPrivateData, audioDescriptor);
+ jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
+ dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
+ mpuSequenceNumber, isPesPrivateData, audioDescriptor);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -733,16 +734,17 @@
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/DownloadEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIII)V");
+ jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIIII)V");
const DemuxFilterDownloadEvent &downloadEvent = event.get<DemuxFilterEvent::Tag::download>();
jint itemId = downloadEvent.itemId;
+ jint downloadId = downloadEvent.downloadId;
jint mpuSequenceNumber = downloadEvent.mpuSequenceNumber;
jint itemFragmentIndex = downloadEvent.itemFragmentIndex;
jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
jint dataLength = downloadEvent.dataLength;
- jobject obj = env->NewObject(eventClazz, eventInit, itemId, mpuSequenceNumber,
+ jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
itemFragmentIndex, lastItemFragmentIndex, dataLength);
env->SetObjectArrayElement(arr, size, obj);
}
@@ -2507,7 +2509,14 @@
env->SetObjectField(statusObj, field, newIntegerObj);
break;
}
- default: {
+ case FrontendStatus::Tag::streamIdList: {
+ jfieldID field = env->GetFieldID(clazz, "mStreamIds", "[I");
+ std::vector<int32_t> ids = s.get<FrontendStatus::Tag::streamIdList>();
+
+ jintArray valObj = env->NewIntArray(v.size());
+ env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+
+ env->SetObjectField(statusObj, field, valObj);
break;
}
}
@@ -3540,10 +3549,13 @@
static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) {
jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings");
+ bool useDownloadId =
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mUseDownloadId", "Z"));
int32_t downloadId = env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I"));
- DemuxFilterDownloadSettings filterDownloadSettings {
- .downloadId = downloadId,
+ DemuxFilterDownloadSettings filterDownloadSettings{
+ .useDownloadId = useDownloadId,
+ .downloadId = downloadId,
};
return filterDownloadSettings;
}
@@ -4324,6 +4336,17 @@
return (jlong)dvrClient->readFromFile(size);
}
+static jlong android_media_tv_Tuner_seek_dvr(JNIEnv *env, jobject dvr, jlong pos) {
+ sp<DvrClient> dvrClient = getDvrClient(env, dvr);
+ if (dvrClient == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to seek dvr: dvr client not found");
+ return -1;
+ }
+
+ return (jlong)dvrClient->seekFile(pos);
+}
+
static jlong android_media_tv_Tuner_read_dvr_from_array(
JNIEnv* env, jobject dvr, jbyteArray buffer, jlong offset, jlong size) {
sp<DvrClient> dvrClient = getDvrClient(env, dvr);
@@ -4478,38 +4501,37 @@
{ "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
{ "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend },
{ "nativeCloseDemux", "(I)I", (void *)android_media_tv_Tuner_close_demux },
- {"nativeOpenSharedFilter",
+ { "nativeOpenSharedFilter",
"(Ljava/lang/String;)Landroid/media/tv/tuner/filter/SharedFilter;",
(void *)android_media_tv_Tuner_open_shared_filter},
};
static const JNINativeMethod gFilterMethods[] = {
{ "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I",
- (void *)android_media_tv_Tuner_configure_filter },
- { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
- { "nativeGetId64Bit", "()J",
- (void *)android_media_tv_Tuner_get_filter_64bit_id },
+ (void *)android_media_tv_Tuner_configure_filter},
+ { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id},
+ { "nativeGetId64Bit", "()J", (void *)android_media_tv_Tuner_get_filter_64bit_id},
{ "nativeConfigureMonitorEvent", "(I)I",
- (void *)android_media_tv_Tuner_configure_monitor_event },
+ (void *)android_media_tv_Tuner_configure_monitor_event},
{ "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
- (void *)android_media_tv_Tuner_set_filter_data_source },
- { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
- { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter },
- { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
- { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
- { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
- {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
+ (void *)android_media_tv_Tuner_set_filter_data_source},
+ { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+ { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+ { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+ { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+ { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter},
+ { "nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
(void *)android_media_tv_Tuner_acquire_shared_filter_token},
- {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
+ { "nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
(void *)android_media_tv_Tuner_free_shared_filter_token},
};
static const JNINativeMethod gSharedFilterMethods[] = {
- {"nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
- {"nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
- {"nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
- {"nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
- {"nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
+ { "nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+ { "nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+ { "nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+ { "nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+ { "nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
};
static const JNINativeMethod gTimeFilterMethods[] = {
@@ -4549,18 +4571,19 @@
static const JNINativeMethod gDvrPlaybackMethods[] = {
{ "nativeAttachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
- (void *)android_media_tv_Tuner_attach_filter },
+ (void *)android_media_tv_Tuner_attach_filter},
{ "nativeDetachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
- (void *)android_media_tv_Tuner_detach_filter },
+ (void *)android_media_tv_Tuner_detach_filter},
{ "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
- (void *)android_media_tv_Tuner_configure_dvr },
- { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr },
- { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
- { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
- { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr },
- { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd },
- { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr },
- { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array },
+ (void *)android_media_tv_Tuner_configure_dvr},
+ { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr},
+ { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr},
+ { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr},
+ { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr},
+ { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd},
+ { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr},
+ { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array},
+ { "nativeSeek", "(J)J", (void *)android_media_tv_Tuner_seek_dvr},
};
static const JNINativeMethod gLnbMethods[] = {
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 3027838..05683b6 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -22,6 +22,8 @@
#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
#include <android-base/logging.h>
#include <inttypes.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <utils/Log.h>
#include "ClientHelper.h"
@@ -200,6 +202,14 @@
return size;
}
+int64_t DvrClient::seekFile(int64_t pos) {
+ if (mFd < 0) {
+ ALOGE("Failed to seekFile. File is not configured");
+ return -1;
+ }
+ return lseek64(mFd, pos, SEEK_SET);
+}
+
Result DvrClient::configure(DvrSettings settings) {
if (mTunerDvr != nullptr) {
Status s = mTunerDvr->configure(settings);
diff --git a/media/jni/tuner/DvrClient.h b/media/jni/tuner/DvrClient.h
index 9080c72..61c0325 100644
--- a/media/jni/tuner/DvrClient.h
+++ b/media/jni/tuner/DvrClient.h
@@ -82,6 +82,11 @@
int64_t writeToFile(int64_t size);
/**
+ * Seeks the Dvr file descriptor from the beginning of the file.
+ */
+ int64_t seekFile(int64_t pos);
+
+ /**
* Write data to the given buffer with given size. Return the actual write size.
*/
int64_t writeToBuffer(int8_t* buffer, int64_t size);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 6dc05ad..a2eae2c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -303,6 +303,11 @@
public void onCameraClosed(String cameraId) {
Log.v(TAG, String.format("Camera %s is closed", cameraId));
}
+ @Override
+ public void onTorchStrengthLevelChanged(String cameraId, int torchStrength) {
+ Log.v(TAG, String.format("Camera " + cameraId + " torch strength level changed to "
+ + torchStrength ));
+ }
}
/**
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index c5926a5..06f2d9d 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -19,10 +19,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.companiondevicemanager">
- <permission
- android:name="com.android.companiondevicemanager.permission.BIND"
- android:protectionLevel="signature" />
-
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
@@ -43,23 +39,17 @@
android:forceQueryable="true"
android:supportsRtl="true">
- <service
- android:name=".CompanionDeviceDiscoveryService"
- android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
- android:exported="true">
- </service>
-
<activity
android:name=".CompanionDeviceActivity"
- android:theme="@style/ChooserActivity"
+ android:exported="true"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"
android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
- android:exported="true">
- <!--TODO include url scheme filter similar to PrintSpooler -->
- <intent-filter>
- <action android:name="android.companiondevice.START_DISCOVERY" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
+ android:theme="@style/ChooserActivity"/>
+
+ <service
+ android:name=".CompanionDeviceDiscoveryService"
+ android:exported="false" />
</application>
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
new file mode 100644
index 0000000..c87bac6
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/dialog_background"
+ android:elevation="16dp"
+ android:maxHeight="400dp"
+ android:orientation="vertical"
+ android:padding="18dp"
+ android:layout_gravity="center">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingHorizontal="12dp"
+ style="@*android:style/TextAppearance.Widget.Toolbar.Title"/>
+ <!-- style="@*android:style/TextAppearance.Widget.Toolbar.Title" -->
+
+ <TextView
+ android:id="@+id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ android:gravity="center"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="14sp" />
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <ListView
+ android:id="@+id/device_list"
+ style="@android:style/Widget.Material.ListView"
+ android:layout_width="match_parent"
+ android:layout_height="200dp" />
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="end">
+
+ <Button
+ android:id="@+id/button_cancel"
+ style="@android:style/Widget.Material.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/consent_no"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <Button
+ android:id="@+id/button_allow"
+ style="@android:style/Widget.Material.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/consent_yes" />
+
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/buttons.xml b/packages/CompanionDeviceManager/res/layout/buttons.xml
deleted file mode 100644
index a80720c..0000000
--- a/packages/CompanionDeviceManager/res/layout/buttons.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/buttons"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:gravity="end"
->
- <Button
- android:id="@+id/button_cancel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/consent_no"
- android:textColor="?android:attr/textColorSecondary"
- style="@android:style/Widget.Material.Button.Borderless.Colored"
- />
- <Button
- android:id="@+id/button_pair"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/consent_yes"
- style="@android:style/Widget.Material.Button.Borderless.Colored"
- />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/device_chooser.xml b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
deleted file mode 100644
index 273347a..0000000
--- a/packages/CompanionDeviceManager/res/layout/device_chooser.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/container"
- android:layout_height="400dp"
- style="@style/ContainerLayout"
- >
-
- <include layout="@layout/title" />
-
- <include layout="@layout/profile_summary" />
-
- <ListView
- android:id="@+id/device_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@+id/profile_summary"
- android:layout_above="@+id/buttons"
- style="@android:style/Widget.Material.ListView"
- />
-
- <include layout="@layout/buttons" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml b/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
deleted file mode 100644
index 1336e79..0000000
--- a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/container"
- android:layout_height="wrap_content"
- style="@style/ContainerLayout"
- >
-
- <include layout="@layout/title" />
-
- <include layout="@layout/profile_summary" />
-
- <include layout="@layout/buttons" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/profile_summary.xml b/packages/CompanionDeviceManager/res/layout/profile_summary.xml
deleted file mode 100644
index 80fec59..0000000
--- a/packages/CompanionDeviceManager/res/layout/profile_summary.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/profile_summary"
- android:layout_below="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="14sp"
- android:gravity="center"
-/>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/title.xml b/packages/CompanionDeviceManager/res/layout/title.xml
deleted file mode 100644
index 9a50366..0000000
--- a/packages/CompanionDeviceManager/res/layout/title.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- style="@*android:style/TextAppearance.Widget.Toolbar.Title"
-/>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/dimens.xml b/packages/CompanionDeviceManager/res/values/dimens.xml
deleted file mode 100644
index da7b0d1..0000000
--- a/packages/CompanionDeviceManager/res/values/dimens.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <!-- Padding applied on most UI elements -->
- <dimen name="padding">12dp</dimen>
-
-</resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 44748e9..cb8b616 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -19,25 +19,58 @@
<!-- Title of the CompanionDeviceManager application. [CHAR LIMIT=50] -->
<string name="app_label">Companion Device Manager</string>
- <!-- Title of the device selection dialog. -->
- <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by <strong><xliff:g id="app_name" example="Android Wear">%2$s</xliff:g></strong></string>
+ <!-- Title of the device association confirmation dialog. -->
+ <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
- <!-- The generic placeholder for a device type when nothing specific is known about it [CHAR LIMIT=30] -->
- <string name="profile_name_generic">device</string>
+ <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
<!-- The name of the "watch" device type [CHAR LIMIT=30] -->
<string name="profile_name_watch">watch</string>
- <!-- Title of the device association confirmation dialog. -->
- <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
+ <!-- Title of the device selection dialog. -->
+ <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by <strong><xliff:g id="app_name" example="Android Wear">%2$s</xliff:g></strong></string>
- <!-- Text of the device profile permissions explanation in the association dialog. -->
- <string name="profile_summary">This app is needed to manage your <xliff:g id="profile_name" example="watch">%1$s</xliff:g>. <xliff:g id="privileges_discplaimer" example="Android Wear will get access to your Notifications, Calendar and Contacts.">%2$s</xliff:g></string>
+ <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_watch" product="default"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_watch" product="tablet"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.</string>
+
+ <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
+
+ <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+ <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream applications?</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_app_streaming" product="default">Let <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to provide <strong><xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g></strong> remote access to access to applications installed on this phone when connected.</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_app_streaming" product="tablet">Let <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to provide <strong><xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g></strong> remote access to access to applications installed on this tablet when connected.</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_app_streaming" product="device">Let <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to provide <strong><xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g></strong> remote access to access to applications installed on this device when connected.</string>
+
+ <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
+
+ <!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
+ <string name="title_automotive_projection"></string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_automotive_projection"></string>
+
+ <!-- ================= null profile ================= -->
+
+ <!-- A noun for a companion device with unspecified profile (type) [CHAR LIMIT=30] -->
+ <string name="profile_name_generic">device</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_generic"></string>
+
+ <!-- ================= Buttons ================= -->
<!-- Positive button for the device-app association consent dialog [CHAR LIMIT=30] -->
<string name="consent_yes">Allow</string>
<!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
<string name="consent_no">Don\u2019t allow</string>
-
</resources>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
deleted file mode 100644
index 9dced47b..0000000
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <style name="ContainerLayout">
- <item name="android:orientation">vertical</item>
- <item name="android:layout_width">match_parent</item>
- <item name="android:elevation">16dp</item>
- <item name="android:background">@drawable/dialog_background</item>
- <item name="android:paddingTop">18dip</item>
- <item name="android:paddingStart">20dip</item>
- <item name="android:paddingEnd">16dip</item>
- <item name="android:paddingBottom">16dip</item>
- <item name="android:layout_gravity">center</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index a5168cc..cc887c3 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -16,327 +16,356 @@
package com.android.companiondevicemanager;
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
-import static android.text.TextUtils.emptyIfNull;
-import static android.text.TextUtils.isEmpty;
-import static android.text.TextUtils.withoutPrefix;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.companiondevicemanager.Utils.getApplicationLabel;
+import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
+import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
+
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
+import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
+import android.companion.IAssociationRequestCallback;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
+import android.net.MacAddress;
import android.os.Bundle;
-import android.text.Html;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.Spanned;
import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.widget.Button;
import android.widget.ListView;
-import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DeviceFilterPair;
-import com.android.internal.util.Preconditions;
-
public class CompanionDeviceActivity extends Activity {
-
private static final boolean DEBUG = false;
- private static final String LOG_TAG = CompanionDeviceActivity.class.getSimpleName();
+ private static final String TAG = CompanionDeviceActivity.class.getSimpleName();
- static CompanionDeviceActivity sInstance;
+ // Keep the following constants in sync with
+ // frameworks/base/services/companion/java/
+ // com/android/server/companion/AssociationRequestsProcessor.java
- View mLoadingIndicator = null;
- ListView mDeviceListView;
- private View mPairButton;
- private View mCancelButton;
+ // AssociationRequestsProcessor <-> UI
+ private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
+ private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
+ private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
- DevicesAdapter mDevicesAdapter;
+ // AssociationRequestsProcessor -> UI
+ private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
+ private static final String EXTRA_ASSOCIATION = "association";
+
+ // UI -> AssociationRequestsProcessor
+ private static final int RESULT_CODE_ASSOCIATION_APPROVED = 0;
+ private static final String EXTRA_MAC_ADDRESS = "mac_address";
+
+ private AssociationRequest mRequest;
+ private IAssociationRequestCallback mAppCallback;
+ private ResultReceiver mCdmServiceReceiver;
+
+ // Always present widgets.
+ private TextView mTitle;
+ private TextView mSummary;
+
+ // Progress indicator is only shown while we are looking for the first suitable device for a
+ // "regular" (ie. not self-managed) association.
+ private View mProgressIndicator;
+
+ // Present for self-managed association requests and "single-device" regular association
+ // regular.
+ private Button mButtonAllow;
+
+ // The list is only shown for multiple-device regular association request, after at least one
+ // matching device is found.
+ private @Nullable ListView mListView;
+ private @Nullable DeviceListAdapter mAdapter;
+
+ // The flag used to prevent double taps, that may lead to sending several requests for creating
+ // an association to CDM.
+ private boolean mAssociationApproved;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- Log.i(LOG_TAG, "Starting UI for " + getService().mRequest);
-
- if (getService().mDevicesFound.isEmpty()) {
- Log.e(LOG_TAG, "About to show UI, but no devices to show");
- }
-
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- sInstance = this;
- getService().mActivity = this;
-
- String deviceProfile = getRequest().getDeviceProfile();
- String profilePrivacyDisclaimer = emptyIfNull(getRequest()
- .getDeviceProfilePrivilegesDescription())
- .replace("APP_NAME", getCallingAppName());
- boolean useDeviceProfile = deviceProfile != null && !isEmpty(profilePrivacyDisclaimer);
- String profileName = useDeviceProfile
- ? getDeviceProfileName(deviceProfile)
- : getString(R.string.profile_name_generic);
-
- if (getRequest().isSingleDevice()) {
- setContentView(R.layout.device_confirmation);
- final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
- setTitle(Html.fromHtml(getString(
- R.string.confirmation_title,
- Html.escapeHtml(getCallingAppName()),
- Html.escapeHtml(selectedDevice.getDisplayName())), 0));
-
- mPairButton = findViewById(R.id.button_pair);
- mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice));
- getService().mSelectedDevice = selectedDevice;
- onSelectionUpdate();
- if (getRequest().isSkipPrompt()) {
- onDeviceConfirmed(selectedDevice);
- }
- } else {
- setContentView(R.layout.device_chooser);
- mPairButton = findViewById(R.id.button_pair);
- mPairButton.setVisibility(View.GONE);
- setTitle(Html.fromHtml(getString(R.string.chooser_title,
- Html.escapeHtml(profileName),
- Html.escapeHtml(getCallingAppName())), 0));
- mDeviceListView = findViewById(R.id.device_list);
- mDevicesAdapter = new DevicesAdapter();
- mDeviceListView.setAdapter(mDevicesAdapter);
- mDeviceListView.setOnItemClickListener((adapterView, view, pos, l) -> {
- getService().mSelectedDevice =
- (DeviceFilterPair) adapterView.getItemAtPosition(pos);
- mDevicesAdapter.notifyDataSetChanged();
- });
- mDevicesAdapter.registerDataSetObserver(new DataSetObserver() {
- @Override
- public void onChanged() {
- onSelectionUpdate();
- }
- });
- mDeviceListView.addFooterView(mLoadingIndicator = getProgressBar(), null, false);
- }
-
- TextView profileSummary = findViewById(R.id.profile_summary);
-
- if (useDeviceProfile) {
- profileSummary.setVisibility(View.VISIBLE);
- String deviceRef = getRequest().isSingleDevice()
- ? getService().mDevicesFound.get(0).getDisplayName()
- : profileName;
- profileSummary.setText(getString(R.string.profile_summary,
- deviceRef,
- profilePrivacyDisclaimer));
- } else {
- profileSummary.setVisibility(View.GONE);
- }
-
- mCancelButton = findViewById(R.id.button_cancel);
- mCancelButton.setOnClickListener(v -> cancel());
}
- static void notifyDevicesChanged() {
- if (sInstance != null && sInstance.mDevicesAdapter != null && !sInstance.isFinishing()) {
- sInstance.mDevicesAdapter.notifyDataSetChanged();
- }
- }
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (DEBUG) Log.d(TAG, "onStart()");
- private AssociationRequest getRequest() {
- return getService().mRequest;
- }
+ final Intent intent = getIntent();
+ mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+ mAppCallback = IAssociationRequestCallback.Stub.asInterface(
+ intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
+ mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
- private String getDeviceProfileName(@Nullable String deviceProfile) {
- if (deviceProfile == null) {
- return getString(R.string.profile_name_generic);
- }
- switch (deviceProfile) {
- case AssociationRequest.DEVICE_PROFILE_WATCH: {
- return getString(R.string.profile_name_watch);
- }
- default: {
- Log.w(LOG_TAG,
- "No localized profile name found for device profile: " + deviceProfile);
- return withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile)
- .toLowerCase()
- .replace('_', ' ');
- }
- }
- }
+ requireNonNull(mRequest);
+ requireNonNull(mAppCallback);
+ requireNonNull(mCdmServiceReceiver);
- private void cancel() {
- Log.i(LOG_TAG, "cancel()");
- getService().onCancel();
- setResult(RESULT_CANCELED);
- finish();
+ // Start discovery services if needed.
+ if (!mRequest.isSelfManaged()) {
+ CompanionDeviceDiscoveryService.startForRequest(this, mRequest);
+ }
+ // Init UI.
+ initUI();
}
@Override
protected void onStop() {
super.onStop();
- if (!isFinishing() && !isChangingConfigurations()) {
- Log.i(LOG_TAG, "onStop() - cancelling");
- cancel();
+ if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
+
+ // TODO: handle config changes without cancelling.
+ if (!isFinishing()) {
+ cancel(); // will finish()
}
+
+ // mAdapter may be observing - need to remove it.
+ CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.deleteObservers();
}
@Override
- protected void onDestroy() {
- super.onDestroy();
- getService().mActivity = null;
- if (sInstance == this) {
- sInstance = null;
- }
- }
+ protected void onNewIntent(Intent intent) {
+ // Handle another incoming request (while we are not done with the original - mRequest -
+ // yet).
+ final AssociationRequest request = requireNonNull(
+ intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST));
+ if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request);
- private CharSequence getCallingAppName() {
+ // We can only "process" one request at a time.
+ final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub
+ .asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
try {
- final PackageManager packageManager = getPackageManager();
- String callingPackage = Preconditions.checkStringNotEmpty(
- getCallingPackage(),
- "This activity must be called for result");
- return packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(callingPackage, 0));
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException(e);
+ requireNonNull(appCallback).onFailure("Busy.");
+ } catch (RemoteException ignore) {
}
}
- @Override
- public String getCallingPackage() {
- return requireNonNull(getRequest().getCallingPackage());
- }
+ private void initUI() {
+ if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
- @Override
- public void setTitle(CharSequence title) {
- final TextView titleView = findViewById(R.id.title);
- final int padding = getPadding(getResources());
- titleView.setPadding(padding, padding, padding, padding);
- titleView.setText(title);
- }
+ setContentView(R.layout.activity_confirmation);
- private ProgressBar getProgressBar() {
- final ProgressBar progressBar = new ProgressBar(this);
- progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL);
- final int padding = getPadding(getResources());
- progressBar.setPadding(padding, padding, padding, padding);
- return progressBar;
- }
+ mTitle = findViewById(R.id.title);
+ mSummary = findViewById(R.id.summary);
- static int getPadding(Resources r) {
- return r.getDimensionPixelSize(R.dimen.padding);
- }
+ mListView = findViewById(R.id.device_list);
+ mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position));
- private void onSelectionUpdate() {
- DeviceFilterPair selectedDevice = getService().mSelectedDevice;
- if (mPairButton.getVisibility() != View.VISIBLE && selectedDevice != null) {
- onDeviceConfirmed(selectedDevice);
+ mButtonAllow = findViewById(R.id.button_allow);
+ mButtonAllow.setOnClickListener(this::onAllowButtonClick);
+
+ findViewById(R.id.button_cancel).setOnClickListener(v -> cancel());
+
+ final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
+ if (mRequest.isSelfManaged()) {
+ initUiForSelfManagedAssociation(appLabel);
+ } else if (mRequest.isSingleDevice()) {
+ initUiForSingleDevice(appLabel);
} else {
- mPairButton.setEnabled(selectedDevice != null);
+ initUiForMultipleDevices(appLabel);
}
}
- private CompanionDeviceDiscoveryService getService() {
- return CompanionDeviceDiscoveryService.sInstance;
+ private void onAssociationCreated(@NonNull AssociationInfo association) {
+ if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
+
+ // Don't need to notify the app, CdmService has already done that. Just finish.
+ setResultAndFinish(association);
}
- protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) {
- Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")");
- getService().onDeviceSelected(
- getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
+ private void cancel() {
+ if (DEBUG) Log.i(TAG, "cancel()");
+
+ // Stop discovery service if it was used.
+ if (!mRequest.isSelfManaged()) {
+ CompanionDeviceDiscoveryService.stop(this);
+ }
+
+ // First send callback to the app directly...
+ try {
+ mAppCallback.onFailure("Cancelled.");
+ } catch (RemoteException ignore) {
+ }
+
+ // ... then set result and finish ("sending" onActivityResult()).
+ setResultAndFinish(null);
}
- void setResultAndFinish() {
- Log.i(LOG_TAG, "setResultAndFinish(selectedDevice = "
- + getService().mSelectedDevice.device + ")");
- setResult(RESULT_OK,
- new Intent().putExtra(
- CompanionDeviceManager.EXTRA_DEVICE, getService().mSelectedDevice.device));
+ private void setResultAndFinish(@Nullable AssociationInfo association) {
+ if (DEBUG) Log.i(TAG, "setResultAndFinish(), association=" + association);
+
+ final Intent data = new Intent();
+ if (association != null) {
+ data.putExtra(CompanionDeviceManager.EXTRA_ASSOCIATION, association);
+ if (!association.isSelfManaged()) {
+ data.putExtra(CompanionDeviceManager.EXTRA_DEVICE,
+ association.getDeviceMacAddressAsString());
+ }
+ }
+ setResult(association != null ? RESULT_OK : RESULT_CANCELED, data);
+
finish();
}
- class DevicesAdapter extends BaseAdapter {
- private final Drawable mBluetoothIcon = icon(android.R.drawable.stat_sys_data_bluetooth);
- private final Drawable mWifiIcon = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
+ private void initUiForSelfManagedAssociation(CharSequence appLabel) {
+ if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
- private SparseArray<Integer> mColors = new SparseArray();
+ final CharSequence deviceName = mRequest.getDisplayName(); // "<device>";
+ final String deviceProfile = mRequest.getDeviceProfile(); // DEVICE_PROFILE_APP_STREAMING;
- private Drawable icon(int drawableRes) {
- Drawable icon = getResources().getDrawable(drawableRes, null);
- icon.setTint(Color.DKGRAY);
- return icon;
+ final Spanned title;
+ final Spanned summary;
+ switch (deviceProfile) {
+ case DEVICE_PROFILE_APP_STREAMING:
+ title = getHtmlFromResources(this, R.string.title_app_streaming, appLabel);
+ summary = getHtmlFromResources(
+ this, R.string.summary_app_streaming, appLabel, deviceName);
+ break;
+
+ case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
+ title = getHtmlFromResources(this, R.string.title_automotive_projection, appLabel);
+ summary = getHtmlFromResources(
+ this, R.string.summary_automotive_projection, appLabel, deviceName);
+ break;
+
+ default:
+ throw new RuntimeException("Unsupported profile " + deviceProfile);
}
+ mTitle.setText(title);
+ mSummary.setText(summary);
- @Override
- public View getView(
- int position,
- @Nullable View convertView,
- @NonNull ViewGroup parent) {
- TextView view = convertView instanceof TextView
- ? (TextView) convertView
- : newView();
- bind(view, getItem(position));
- return view;
- }
-
- private void bind(TextView textView, DeviceFilterPair device) {
- textView.setText(device.getDisplayName());
- textView.setBackgroundColor(
- device.equals(getService().mSelectedDevice)
- ? getColor(android.R.attr.colorControlHighlight)
- : Color.TRANSPARENT);
- textView.setCompoundDrawablesWithIntrinsicBounds(
- device.device instanceof android.net.wifi.ScanResult
- ? mWifiIcon
- : mBluetoothIcon,
- null, null, null);
- textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground));
- }
-
- private TextView newView() {
- final TextView textView = new TextView(CompanionDeviceActivity.this);
- textView.setTextColor(getColor(android.R.attr.colorForeground));
- final int padding = CompanionDeviceActivity.getPadding(getResources());
- textView.setPadding(padding, padding, padding, padding);
- textView.setCompoundDrawablePadding(padding);
- return textView;
- }
-
- private int getColor(int colorAttr) {
- if (mColors.contains(colorAttr)) {
- return mColors.get(colorAttr);
- }
- TypedValue typedValue = new TypedValue();
- TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr });
- int result = a.getColor(0, 0);
- a.recycle();
- mColors.put(colorAttr, result);
- return result;
- }
-
- @Override
- public int getCount() {
- return getService().mDevicesFound.size();
- }
-
- @Override
- public DeviceFilterPair getItem(int position) {
- return getService().mDevicesFound.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
+ mListView.setVisibility(View.GONE);
}
+
+ private void initUiForSingleDevice(CharSequence appLabel) {
+ if (DEBUG) Log.i(TAG, "initUiFor_SingleDevice()");
+
+ // TODO: use real name
+ final String deviceName = "<device>";
+ final String deviceProfile = mRequest.getDeviceProfile();
+
+ final Spanned title = getHtmlFromResources(
+ this, R.string.confirmation_title, appLabel, deviceName);
+ final Spanned summary;
+ if (deviceProfile == null) {
+ summary = getHtmlFromResources(this, R.string.summary_generic);
+ } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
+ summary = getHtmlFromResources(this, R.string.summary_watch, appLabel, deviceName);
+ } else {
+ throw new RuntimeException("Unsupported profile " + deviceProfile);
+ }
+
+ mTitle.setText(title);
+ mSummary.setText(summary);
+
+ mListView.setVisibility(View.GONE);
+ }
+
+ private void initUiForMultipleDevices(CharSequence appLabel) {
+ if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
+
+ final String deviceProfile = mRequest.getDeviceProfile();
+
+ final String profileName;
+ final Spanned summary;
+ if (deviceProfile == null) {
+ profileName = getString(R.string.profile_name_generic);
+ summary = getHtmlFromResources(this, R.string.summary_generic);
+ } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
+ profileName = getString(R.string.profile_name_watch);
+ summary = getHtmlFromResources(this, R.string.summary_watch, appLabel);
+ } else {
+ throw new RuntimeException("Unsupported profile " + deviceProfile);
+ }
+ final Spanned title = getHtmlFromResources(
+ this, R.string.chooser_title, profileName, appLabel);
+
+ mTitle.setText(title);
+ mSummary.setText(summary);
+
+ mAdapter = new DeviceListAdapter(this);
+ CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
+ // TODO: hide the list and show a spinner until a first device matching device is found.
+ mListView.setAdapter(mAdapter);
+
+ // "Remove" consent button: users would need to click on the list item.
+ mButtonAllow.setVisibility(View.GONE);
+ }
+
+ private void onListItemClick(int position) {
+ if (DEBUG) Log.d(TAG, "onListItemClick() " + position);
+
+ final DeviceFilterPair<?> selectedDevice = mAdapter.getItem(position);
+ final MacAddress macAddress = selectedDevice.getMacAddress();
+ onAssociationApproved(macAddress);
+ }
+
+ private void onAllowButtonClick(View v) {
+ if (DEBUG) Log.d(TAG, "onAllowButtonClick()");
+
+ // Disable the button, to prevent more clicks.
+ v.setEnabled(false);
+
+ final MacAddress macAddress;
+ if (mRequest.isSelfManaged()) {
+ macAddress = null;
+ } else {
+ // TODO: implement.
+ throw new UnsupportedOperationException(
+ "isSingleDevice() requests are not supported yet.");
+ }
+ onAssociationApproved(macAddress);
+ }
+
+ private void onAssociationApproved(@Nullable MacAddress macAddress) {
+ if (mAssociationApproved) return;
+ mAssociationApproved = true;
+
+ if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
+
+ if (!mRequest.isSelfManaged()) {
+ requireNonNull(macAddress);
+ CompanionDeviceDiscoveryService.stop(this);
+ }
+
+ final Bundle data = new Bundle();
+ data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest);
+ data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder());
+ if (macAddress != null) {
+ data.putParcelable(EXTRA_MAC_ADDRESS, macAddress);
+ }
+
+ data.putParcelable(EXTRA_RESULT_RECEIVER,
+ prepareResultReceiverForIpc(mOnAssociationCreatedReceiver));
+
+ mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
+ }
+
+ private final ResultReceiver mOnAssociationCreatedReceiver =
+ new ResultReceiver(Handler.getMain()) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle data) {
+ if (resultCode != RESULT_CODE_ASSOCIATION_CREATED) {
+ throw new RuntimeException("Unknown result code: " + resultCode);
+ }
+
+ final AssociationInfo association = data.getParcelable(EXTRA_ASSOCIATION);
+ requireNonNull(association);
+
+ onAssociationCreated(association);
+ }
+ };
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 126b823..a4ff1dc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -16,18 +16,17 @@
package com.android.companiondevicemanager;
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
-
+import static com.android.companiondevicemanager.Utils.runOnMainThread;
import static com.android.internal.util.ArrayUtils.isEmpty;
-import static com.android.internal.util.CollectionUtils.emptyIfNull;
-import static com.android.internal.util.CollectionUtils.size;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.internal.util.CollectionUtils.filter;
+import static com.android.internal.util.CollectionUtils.find;
+import static com.android.internal.util.CollectionUtils.map;
+
+import static java.util.Objects.requireNonNull;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -42,8 +41,6 @@
import android.companion.BluetoothDeviceFilter;
import android.companion.BluetoothLeDeviceFilter;
import android.companion.DeviceFilter;
-import android.companion.IAssociationRequestCallback;
-import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.WifiDeviceFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -53,417 +50,411 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.Preconditions;
-
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Observable;
public class CompanionDeviceDiscoveryService extends Service {
-
private static final boolean DEBUG = false;
- private static final String LOG_TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
+ private static final String TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
- private static final long SCAN_TIMEOUT = 20000;
+ private static final String ACTION_START_DISCOVERY =
+ "com.android.companiondevicemanager.action.START_DISCOVERY";
+ private static final String ACTION_STOP_DISCOVERY =
+ "com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY";
+ private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
- static CompanionDeviceDiscoveryService sInstance;
+ private static final long SCAN_TIMEOUT = 20_000L; // 20 seconds
- private BluetoothManager mBluetoothManager;
- private BluetoothAdapter mBluetoothAdapter;
+ // TODO: replace with LiveData-s?
+ static final Observable TIMEOUT_OBSERVABLE = new MyObservable();
+ static final Observable SCAN_RESULTS_OBSERVABLE = new MyObservable();
+
+ private static CompanionDeviceDiscoveryService sInstance;
+
+ private BluetoothManager mBtManager;
+ private BluetoothAdapter mBtAdapter;
private WifiManager mWifiManager;
- @Nullable private BluetoothLeScanner mBLEScanner;
- private ScanSettings mDefaultScanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
- .build();
+ private BluetoothLeScanner mBleScanner;
- private List<DeviceFilter<?>> mFilters;
- private List<BluetoothLeDeviceFilter> mBLEFilters;
- private List<BluetoothDeviceFilter> mBluetoothFilters;
- private List<WifiDeviceFilter> mWifiFilters;
- private List<ScanFilter> mBLEScanFilters;
+ private ScanCallback mBleScanCallback;
+ private BluetoothBroadcastReceiver mBtReceiver;
+ private WifiBroadcastReceiver mWifiReceiver;
- AssociationRequest mRequest;
- List<DeviceFilterPair> mDevicesFound;
- DeviceFilterPair mSelectedDevice;
- IAssociationRequestCallback mApplicationCallback;
+ private boolean mDiscoveryStarted = false;
+ private boolean mDiscoveryStopped = false;
+ private final List<DeviceFilterPair<?>> mDevicesFound = new ArrayList<>();
- AndroidFuture<String> mServiceCallback;
- boolean mIsScanning = false;
- @Nullable
- CompanionDeviceActivity mActivity = null;
+ private final Runnable mTimeoutRunnable = this::timeout;
- private final ICompanionDeviceDiscoveryService mBinder =
- new ICompanionDeviceDiscoveryService.Stub() {
- @Override
- public void startDiscovery(AssociationRequest request,
- String callingPackage,
- IAssociationRequestCallback appCallback,
- AndroidFuture<String> serviceCallback) {
- Log.i(LOG_TAG,
- "startDiscovery() called with: filter = [" + request
- + "], appCallback = [" + appCallback + "]"
- + "], serviceCallback = [" + serviceCallback + "]");
- mApplicationCallback = appCallback;
- mServiceCallback = serviceCallback;
- Handler.getMain().sendMessage(obtainMessage(
- CompanionDeviceDiscoveryService::startDiscovery,
- CompanionDeviceDiscoveryService.this, request));
- }
+ static void startForRequest(
+ @NonNull Context context, @NonNull AssociationRequest associationRequest) {
+ requireNonNull(associationRequest);
+ final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
+ intent.setAction(ACTION_START_DISCOVERY);
+ intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest);
+ context.startService(intent);
+ }
- @Override
- public void onAssociationCreated() {
- Handler.getMain().post(CompanionDeviceDiscoveryService.this::onAssociationCreated);
- }
- };
+ static void stop(@NonNull Context context) {
+ final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
+ intent.setAction(ACTION_STOP_DISCOVERY);
+ context.startService(intent);
+ }
- private ScanCallback mBLEScanCallback;
- private BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
- private WifiBroadcastReceiver mWifiBroadcastReceiver;
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.i(LOG_TAG, "onBind(" + intent + ")");
- return mBinder.asBinder();
+ @MainThread
+ static @NonNull List<DeviceFilterPair<?>> getScanResults() {
+ return sInstance != null ? new ArrayList<>(sInstance.mDevicesFound)
+ : Collections.emptyList();
}
@Override
public void onCreate() {
super.onCreate();
-
- Log.i(LOG_TAG, "onCreate()");
-
- mBluetoothManager = getSystemService(BluetoothManager.class);
- mBluetoothAdapter = mBluetoothManager.getAdapter();
- mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
- mWifiManager = getSystemService(WifiManager.class);
-
- mDevicesFound = new ArrayList<>();
+ if (DEBUG) Log.d(TAG, "onCreate()");
sInstance = this;
- }
- @MainThread
- private void startDiscovery(AssociationRequest request) {
- if (!request.equals(mRequest)) {
- mRequest = request;
-
- mFilters = request.getDeviceFilters();
- mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
- mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
- mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLeDeviceFilter.class);
- mBLEScanFilters
- = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter);
-
- reset();
- } else {
- Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
- }
-
- if (!ArrayUtils.isEmpty(mDevicesFound)) {
- onReadyToShowUI();
- }
-
- // If filtering to get single device by mac address, also search in the set of already
- // bonded devices to allow linking those directly
- String singleMacAddressFilter = null;
- if (mRequest.isSingleDevice()) {
- int numFilters = size(mBluetoothFilters);
- for (int i = 0; i < numFilters; i++) {
- BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
- if (!TextUtils.isEmpty(filter.getAddress())) {
- singleMacAddressFilter = filter.getAddress();
- break;
- }
- }
- }
- if (singleMacAddressFilter != null) {
- for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
- onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
- }
- for (BluetoothDevice dev : emptyIfNull(
- mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT))) {
- onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
- }
- for (BluetoothDevice dev : emptyIfNull(
- mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER))) {
- onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
- }
- }
-
- if (shouldScan(mBluetoothFilters)) {
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
-
- Log.i(LOG_TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
- mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
- registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
- mBluetoothAdapter.startDiscovery();
- }
-
- if (shouldScan(mBLEFilters) && mBLEScanner != null) {
- Log.i(LOG_TAG, "BLEScanner.startScan");
- mBLEScanCallback = new BLEScanCallback();
- mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
- }
-
- if (shouldScan(mWifiFilters)) {
- Log.i(LOG_TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
- mWifiBroadcastReceiver = new WifiBroadcastReceiver();
- registerReceiver(mWifiBroadcastReceiver,
- new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
- mWifiManager.startScan();
- }
- mIsScanning = true;
- Handler.getMain().sendMessageDelayed(
- obtainMessage(CompanionDeviceDiscoveryService::stopScan, this),
- SCAN_TIMEOUT);
- }
-
- @MainThread
- private void onAssociationCreated() {
- mActivity.setResultAndFinish();
- }
-
- private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
- return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
- }
-
- @MainThread
- private void reset() {
- Log.i(LOG_TAG, "reset()");
- stopScan();
- mDevicesFound.clear();
- mSelectedDevice = null;
- CompanionDeviceActivity.notifyDevicesChanged();
+ mBtManager = getSystemService(BluetoothManager.class);
+ mBtAdapter = mBtManager.getAdapter();
+ mBleScanner = mBtAdapter.getBluetoothLeScanner();
+ mWifiManager = getSystemService(WifiManager.class);
}
@Override
- public boolean onUnbind(Intent intent) {
- Log.i(LOG_TAG, "onUnbind(intent = " + intent + ")");
- stopScan();
- return super.onUnbind(intent);
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "onStartCommand() action=" + action);
+
+ switch (action) {
+ case ACTION_START_DISCOVERY:
+ final AssociationRequest request =
+ intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+ startDiscovery(request);
+ break;
+
+ case ACTION_STOP_DISCOVERY:
+ stopDiscoveryAndFinish();
+ break;
+ }
+ return START_NOT_STICKY;
}
- private void stopScan() {
- Log.i(LOG_TAG, "stopScan()");
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DEBUG) Log.d(TAG, "onDestroy()");
- if (!mIsScanning) return;
- mIsScanning = false;
-
- if (mActivity != null && mActivity.mDeviceListView != null) {
- mActivity.mDeviceListView.removeFooterView(mActivity.mLoadingIndicator);
- }
-
- mBluetoothAdapter.cancelDiscovery();
- if (mBluetoothBroadcastReceiver != null) {
- unregisterReceiver(mBluetoothBroadcastReceiver);
- mBluetoothBroadcastReceiver = null;
- }
- if (mBLEScanner != null) mBLEScanner.stopScan(mBLEScanCallback);
- if (mWifiBroadcastReceiver != null) {
- unregisterReceiver(mWifiBroadcastReceiver);
- mWifiBroadcastReceiver = null;
- }
- }
-
- private void onDeviceFound(@Nullable DeviceFilterPair device) {
- if (device == null) return;
-
- Handler.getMain().sendMessage(obtainMessage(
- CompanionDeviceDiscoveryService::onDeviceFoundMainThread, this, device));
+ sInstance = null;
}
@MainThread
- void onDeviceFoundMainThread(@NonNull DeviceFilterPair device) {
- if (mDevicesFound.contains(device)) {
- Log.i(LOG_TAG, "Skipping device " + device + " - already among found devices");
- return;
- }
+ private void startDiscovery(@NonNull AssociationRequest request) {
+ if (DEBUG) Log.i(TAG, "startDiscovery() request=" + request);
+ requireNonNull(request);
- Log.i(LOG_TAG, "Found device " + device);
+ if (mDiscoveryStarted) throw new RuntimeException("Discovery in progress.");
+ mDiscoveryStarted = true;
- if (mDevicesFound.isEmpty()) {
- onReadyToShowUI();
- }
- mDevicesFound.add(device);
- CompanionDeviceActivity.notifyDevicesChanged();
- }
+ final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
+ final List<BluetoothDeviceFilter> btFilters =
+ filter(allFilters, BluetoothDeviceFilter.class);
+ final List<BluetoothLeDeviceFilter> bleFilters =
+ filter(allFilters, BluetoothLeDeviceFilter.class);
+ final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
- //TODO also, on timeout -> call onFailure
- private void onReadyToShowUI() {
- try {
- mApplicationCallback.onAssociationPending(PendingIntent.getActivity(
- this, 0,
- new Intent(this, CompanionDeviceActivity.class),
- PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
+ checkBoundDevicesIfNeeded(request, btFilters);
- private void onDeviceLost(@Nullable DeviceFilterPair device) {
- Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
- Handler.getMain().sendMessage(obtainMessage(
- CompanionDeviceDiscoveryService::onDeviceLostMainThread, this, device));
+ // If no filters are specified: look for everything.
+ final boolean forceStartScanningAll = isEmpty(allFilters);
+ // Start BT scanning (if needed)
+ mBtReceiver = startBtScanningIfNeeded(btFilters, forceStartScanningAll);
+ // Start Wi-Fi scanning (if needed)
+ mWifiReceiver = startWifiScanningIfNeeded(wifiFilters, forceStartScanningAll);
+ // Start BLE scanning (if needed)
+ mBleScanCallback = startBleScanningIfNeeded(bleFilters, forceStartScanningAll);
+
+ // Schedule a time-out.
+ Handler.getMain().postDelayed(mTimeoutRunnable, SCAN_TIMEOUT);
}
@MainThread
- void onDeviceLostMainThread(@Nullable DeviceFilterPair device) {
- mDevicesFound.remove(device);
- CompanionDeviceActivity.notifyDevicesChanged();
- }
+ private void stopDiscoveryAndFinish() {
+ if (DEBUG) Log.i(TAG, "stopDiscovery()");
- void onDeviceSelected(String callingPackage, String deviceAddress) {
- if (callingPackage == null || deviceAddress == null) {
+ if (!mDiscoveryStarted) {
+ stopSelf();
return;
}
- mServiceCallback.complete(deviceAddress);
- }
- void onCancel() {
- if (DEBUG) Log.i(LOG_TAG, "onCancel()");
- mActivity = null;
- mServiceCallback.cancel(true);
- }
+ if (mDiscoveryStopped) return;
+ mDiscoveryStopped = true;
- /**
- * A pair of device and a filter that matched this device if any.
- *
- * @param <T> device type
- */
- static class DeviceFilterPair<T extends Parcelable> {
- public final T device;
- @Nullable
- public final DeviceFilter<T> filter;
-
- private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
- this.device = device;
- this.filter = filter;
+ // Stop BT discovery.
+ if (mBtReceiver != null) {
+ // Cancel discovery.
+ mBtAdapter.cancelDiscovery();
+ // Unregister receiver.
+ unregisterReceiver(mBtReceiver);
+ mBtReceiver = null;
}
- /**
- * {@code (device, null)} if the filters list is empty or null
- * {@code null} if none of the provided filters match the device
- * {@code (device, filter)} where filter is among the list of filters and matches the device
- */
- @Nullable
- public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
- T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
- if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
- final DeviceFilter<T> matchingFilter
- = CollectionUtils.find(filters, f -> f.matches(dev));
-
- DeviceFilterPair<T> result = matchingFilter != null
- ? new DeviceFilterPair<>(dev, matchingFilter)
- : null;
- if (DEBUG) Log.i(LOG_TAG, "findMatch(dev = " + dev + ", filters = " + filters +
- ") -> " + result);
- return result;
+ // Stop Wi-Fi scanning.
+ if (mWifiReceiver != null) {
+ // TODO: need to stop scan?
+ // Unregister receiver.
+ unregisterReceiver(mWifiReceiver);
+ mWifiReceiver = null;
}
- public String getDisplayName() {
- if (filter == null) {
- Preconditions.checkNotNull(device);
- if (device instanceof BluetoothDevice) {
- return getDeviceDisplayNameInternal((BluetoothDevice) device);
- } else if (device instanceof android.net.wifi.ScanResult) {
- return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device);
- } else if (device instanceof ScanResult) {
- return getDeviceDisplayNameInternal(((ScanResult) device).getDevice());
- } else {
- throw new IllegalArgumentException("Unknown device type: " + device.getClass());
- }
+ // Stop BLE scanning.
+ if (mBleScanCallback != null) {
+ mBleScanner.stopScan(mBleScanCallback);
+ }
+
+ Handler.getMain().removeCallbacks(mTimeoutRunnable);
+
+ // "Finish".
+ stopSelf();
+ }
+
+ private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+ @NonNull List<BluetoothDeviceFilter> btFilters) {
+ // If filtering to get single device by mac address, also search in the set of already
+ // bonded devices to allow linking those directly
+ if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+
+ final BluetoothDeviceFilter singleMacAddressFilter =
+ find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
+
+ if (singleMacAddressFilter == null) return;
+
+ findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
+ findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
+ findAndReportMatches(
+ mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+ }
+
+ private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+ @NonNull List<BluetoothDeviceFilter> filters) {
+ if (devices == null) return;
+
+ for (BluetoothDevice device : devices) {
+ final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
+ if (match != null) {
+ onDeviceFound(match);
}
- return filter.getDeviceDisplayName(device);
+ }
+ }
+
+ private BluetoothBroadcastReceiver startBtScanningIfNeeded(
+ List<BluetoothDeviceFilter> filters, boolean force) {
+ if (isEmpty(filters) && !force) return null;
+ if (DEBUG) Log.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
+
+ final BluetoothBroadcastReceiver receiver = new BluetoothBroadcastReceiver(filters);
+
+ final IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
+ registerReceiver(receiver, intentFilter);
+
+ mBtAdapter.startDiscovery();
+
+ return receiver;
+ }
+
+ private WifiBroadcastReceiver startWifiScanningIfNeeded(
+ List<WifiDeviceFilter> filters, boolean force) {
+ if (isEmpty(filters) && !force) return null;
+ if (DEBUG) Log.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
+
+ final WifiBroadcastReceiver receiver = new WifiBroadcastReceiver(filters);
+
+ final IntentFilter intentFilter = new IntentFilter(
+ WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ registerReceiver(receiver, intentFilter);
+
+ mWifiManager.startScan();
+
+ return receiver;
+ }
+
+ private ScanCallback startBleScanningIfNeeded(
+ List<BluetoothLeDeviceFilter> filters, boolean force) {
+ if (isEmpty(filters) && !force) return null;
+ if (DEBUG) Log.d(TAG, "BLEScanner.startScan");
+
+ if (mBleScanner == null) {
+ Log.w(TAG, "BLE Scanner is not available.");
+ return null;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
- return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device));
- }
+ final BLEScanCallback callback = new BLEScanCallback(filters);
- @Override
- public int hashCode() {
- return Objects.hash(getDeviceMacAddress(device));
- }
+ final List<ScanFilter> scanFilters = map(
+ filters, BluetoothLeDeviceFilter::getScanFilter);
+ final ScanSettings scanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build();
+ mBleScanner.startScan(scanFilters, scanSettings, callback);
- @Override
- public String toString() {
- return "DeviceFilterPair{"
- + "device=" + device + " " + getDisplayName()
- + ", filter=" + filter
- + '}';
- }
+ return callback;
+ }
+
+ private void onDeviceFound(@NonNull DeviceFilterPair<?> device) {
+ runOnMainThread(() -> {
+ if (DEBUG) Log.v(TAG, "onDeviceFound() " + device);
+ if (mDevicesFound.contains(device)) {
+ // TODO: update the device instead of ignoring (new found device may contain
+ // additional/updated info, eg. name of the device).
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceFound() " + device.toShortString()
+ + " - Already seen: ignore.");
+ }
+ return;
+ }
+ if (DEBUG) Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+
+ // First: make change.
+ mDevicesFound.add(device);
+ // Then: notify observers.
+ SCAN_RESULTS_OBSERVABLE.notifyObservers();
+ });
+ }
+
+ private void onDeviceLost(@Nullable DeviceFilterPair<?> device) {
+ runOnMainThread(() -> {
+ if (DEBUG) Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+
+ // First: make change.
+ mDevicesFound.remove(device);
+ // Then: notify observers.
+ SCAN_RESULTS_OBSERVABLE.notifyObservers();
+ });
+ }
+
+ private void timeout() {
+ if (DEBUG) Log.i(TAG, "timeout()");
+ stopDiscoveryAndFinish();
+ TIMEOUT_OBSERVABLE.notifyObservers();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
}
private class BLEScanCallback extends ScanCallback {
+ final List<BluetoothLeDeviceFilter> mFilters;
- public BLEScanCallback() {
- if (DEBUG) Log.i(LOG_TAG, "new BLEScanCallback() -> " + this);
+ BLEScanCallback(List<BluetoothLeDeviceFilter> filters) {
+ mFilters = filters;
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
if (DEBUG) {
- Log.i(LOG_TAG,
- "BLE.onScanResult(callbackType = " + callbackType + ", result = " + result
- + ")");
+ Log.v(TAG, "BLE.onScanResult() callback=" + callbackType + ", result=" + result);
}
- final DeviceFilterPair<ScanResult> deviceFilterPair
- = DeviceFilterPair.findMatch(result, mBLEFilters);
- if (deviceFilterPair == null) return;
+
+ final DeviceFilterPair<ScanResult> match = findMatch(result, mFilters);
+ if (match == null) return;
+
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
- onDeviceLost(deviceFilterPair);
+ onDeviceLost(match);
} else {
- onDeviceFound(deviceFilterPair);
+ // TODO: check this logic.
+ onDeviceFound(match);
}
}
}
private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+ final List<BluetoothDeviceFilter> mFilters;
+
+ BluetoothBroadcastReceiver(List<BluetoothDeviceFilter> filters) {
+ this.mFilters = filters;
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.i(LOG_TAG,
- "BL.onReceive(context = " + context + ", intent = " + intent + ")");
- }
+ final String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- final DeviceFilterPair<BluetoothDevice> deviceFilterPair
- = DeviceFilterPair.findMatch(device, mBluetoothFilters);
- if (deviceFilterPair == null) return;
- if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
- onDeviceFound(deviceFilterPair);
+
+ if (DEBUG) Log.v(TAG, action + ", device=" + device);
+
+ if (action == null) return;
+
+ final DeviceFilterPair<BluetoothDevice> match = findMatch(device, mFilters);
+ if (match == null) return;
+
+ if (action.equals(BluetoothDevice.ACTION_FOUND)) {
+ onDeviceFound(match);
} else {
- onDeviceLost(deviceFilterPair);
+ // TODO: check this logic.
+ onDeviceLost(match);
}
}
}
private class WifiBroadcastReceiver extends BroadcastReceiver {
+ final List<WifiDeviceFilter> mFilters;
+
+ private WifiBroadcastReceiver(List<WifiDeviceFilter> filters) {
+ this.mFilters = filters;
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
- List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
+ if (!Objects.equals(intent.getAction(), WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ return;
+ }
- if (DEBUG) {
- Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults));
- }
+ final List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
+ if (DEBUG) {
+ Log.v(TAG, "WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, results:\n "
+ + TextUtils.join("\n ", scanResults));
+ }
- for (int i = 0; i < scanResults.size(); i++) {
- onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
+ for (int i = 0; i < scanResults.size(); i++) {
+ final android.net.wifi.ScanResult scanResult = scanResults.get(i);
+ final DeviceFilterPair<?> match = findMatch(scanResult, mFilters);
+ if (match != null) {
+ onDeviceFound(match);
}
}
}
}
+
+ /**
+ * {@code (device, null)} if the filters list is empty or null
+ * {@code null} if none of the provided filters match the device
+ * {@code (device, filter)} where filter is among the list of filters and matches the device
+ */
+ @Nullable
+ public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
+ T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
+ if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
+ final DeviceFilter<T> matchingFilter = find(filters, f -> f.matches(dev));
+
+ DeviceFilterPair<T> result = matchingFilter != null
+ ? new DeviceFilterPair<>(dev, matchingFilter) : null;
+ if (DEBUG) {
+ Log.v(TAG, "findMatch(dev=" + dev + ", filters=" + filters + ") -> " + result);
+ }
+ return result;
+ }
+
+ private static class MyObservable extends Observable {
+ @Override
+ public void notifyObservers() {
+ setChanged();
+ super.notifyObservers();
+ }
+ }
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceFilterPair.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceFilterPair.java
new file mode 100644
index 0000000..faca1ae
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceFilterPair.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.companiondevicemanager;
+
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.companion.DeviceFilter;
+import android.net.MacAddress;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A pair of device and a filter that matched this device if any.
+ *
+ * @param <T> device type.
+ */
+class DeviceFilterPair<T extends Parcelable> {
+ private final T mDevice;
+ private final @Nullable DeviceFilter<T> mFilter;
+
+ DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
+ this.mDevice = device;
+ this.mFilter = filter;
+ }
+
+ T getDevice() {
+ return mDevice;
+ }
+
+ String getDisplayName() {
+ if (mFilter != null) mFilter.getDeviceDisplayName(mDevice);
+
+ if (mDevice instanceof BluetoothDevice) {
+ return getDeviceDisplayNameInternal((BluetoothDevice) mDevice);
+ } else if (mDevice instanceof android.bluetooth.le.ScanResult) {
+ final android.bluetooth.le.ScanResult bleScanResult =
+ (android.bluetooth.le.ScanResult) mDevice;
+ return getDeviceDisplayNameInternal(bleScanResult.getDevice());
+ } else if (mDevice instanceof android.net.wifi.ScanResult) {
+ final android.net.wifi.ScanResult wifiScanResult =
+ (android.net.wifi.ScanResult) mDevice;
+ return getDeviceDisplayNameInternal(wifiScanResult);
+ } else {
+ throw new IllegalArgumentException("Unknown device type: " + mDevice.getClass());
+ }
+ }
+
+ @NonNull MacAddress getMacAddress() {
+ return MacAddress.fromString(getDeviceMacAddress(getDevice()));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
+ return Objects.equals(getDeviceMacAddress(mDevice), getDeviceMacAddress(that.mDevice));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getDeviceMacAddress(mDevice));
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceFilterPair{"
+ + "device=" + mDevice + " " + getDisplayName()
+ + ", filter=" + mFilter
+ + '}';
+ }
+
+ @NonNull String toShortString() {
+ return '(' + getDeviceTypeAsString() + ") " + getMacAddress() + " '" + getDisplayName()
+ + '\'';
+ }
+
+ private @NonNull String getDeviceTypeAsString() {
+ if (mDevice instanceof BluetoothDevice) {
+ return "BT";
+ } else if (mDevice instanceof android.bluetooth.le.ScanResult) {
+ return "BLE";
+ } else if (mDevice instanceof android.net.wifi.ScanResult) {
+ return "Wi-Fi";
+ } else {
+ return "Unknown";
+ }
+ }
+}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
new file mode 100644
index 0000000..cf2a2bf
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.companiondevicemanager;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+
+/**
+ * Adapter for the list of "found" devices.
+ */
+class DeviceListAdapter extends BaseAdapter implements Observer {
+ private final Context mContext;
+ private final Resources mResources;
+
+ private final Drawable mBluetoothIcon;
+ private final Drawable mWifiIcon;
+
+ private final @ColorInt int mTextColor;
+
+ // List if pairs (display name, address)
+ private List<DeviceFilterPair<?>> mDevices;
+
+ DeviceListAdapter(Context context) {
+ mContext = context;
+ mResources = context.getResources();
+ mBluetoothIcon = getTintedIcon(mResources, android.R.drawable.stat_sys_data_bluetooth);
+ mWifiIcon = getTintedIcon(mResources, com.android.internal.R.drawable.ic_wifi_signal_3);
+ mTextColor = getColor(context, android.R.attr.colorForeground);
+ }
+
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ final TextView view = convertView != null ? (TextView) convertView : newView();
+ bind(view, getItem(position));
+ return view;
+ }
+
+ private void bind(TextView textView, DeviceFilterPair<?> item) {
+ textView.setText(item.getDisplayName());
+ textView.setBackgroundColor(Color.TRANSPARENT);
+ /*
+ textView.setCompoundDrawablesWithIntrinsicBounds(
+ item.getDevice() instanceof android.net.wifi.ScanResult
+ ? mWifiIcon
+ : mBluetoothIcon,
+ null, null, null);
+ textView.getCompoundDrawables()[0].setTint(mTextColor);
+ */
+ }
+
+ private TextView newView() {
+ final TextView textView = new TextView(mContext);
+ textView.setTextColor(mTextColor);
+ final int padding = 24;
+ textView.setPadding(padding, padding, padding, padding);
+ //textView.setCompoundDrawablePadding(padding);
+ return textView;
+ }
+
+ @Override
+ public int getCount() {
+ return mDevices != null ? mDevices.size() : 0;
+ }
+
+ @Override
+ public DeviceFilterPair<?> getItem(int position) {
+ return mDevices.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ mDevices = CompanionDeviceDiscoveryService.getScanResults();
+ notifyDataSetChanged();
+ }
+
+ private @ColorInt int getColor(Context context, int attr) {
+ final TypedArray a = context.obtainStyledAttributes(new TypedValue().data,
+ new int[] { attr });
+ final int color = a.getColor(0, 0);
+ a.recycle();
+ return color;
+ }
+
+ private static Drawable getTintedIcon(Resources resources, int drawableRes) {
+ Drawable icon = resources.getDrawable(drawableRes, null);
+ icon.setTint(Color.DKGRAY);
+ return icon;
+ }
+}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
new file mode 100644
index 0000000..eab421e
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.companiondevicemanager;
+
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.text.Html;
+import android.text.Spanned;
+
+/**
+ * Utilities.
+ */
+class Utils {
+
+ /**
+ * Convert an instance of a "locally-defined" ResultReceiver to an instance of
+ * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
+ * unmarshall.
+ */
+ static <T extends ResultReceiver> ResultReceiver prepareResultReceiverForIpc(T resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
+ }
+
+ static @NonNull CharSequence getApplicationLabel(
+ @NonNull Context context, @NonNull String packageName) {
+ final PackageManager packageManager = context.getPackageManager();
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = packageManager.getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ return packageManager.getApplicationLabel(appInfo);
+ }
+
+ static Spanned getHtmlFromResources(
+ @NonNull Context context, @StringRes int resId, CharSequence... formatArgs) {
+ final String[] escapedArgs = new String[formatArgs.length];
+ for (int i = 0; i < escapedArgs.length; i++) {
+ escapedArgs[i] = Html.escapeHtml(formatArgs[i]);
+ }
+ final String plain = context.getString(resId, (Object[]) escapedArgs);
+ return Html.fromHtml(plain, 0);
+ }
+
+ static void runOnMainThread(Runnable runnable) {
+ if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
+ runnable.run();
+ } else {
+ Handler.getMain().post(runnable);
+ }
+ }
+
+ private Utils() {
+ }
+}
diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS
index 4862377..e267d19 100644
--- a/packages/ConnectivityT/OWNERS
+++ b/packages/ConnectivityT/OWNERS
@@ -1 +1,2 @@
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 3e82b28..931a55b 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -100,6 +100,20 @@
],
}
+// IpSec related libraries.
+
+filegroup {
+ name: "framework-connectivity-ipsec-sources",
+ srcs: [
+ "src/android/net/IIpSecService.aidl",
+ "src/android/net/IpSec*.*",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
// Connectivity-T common libraries.
filegroup {
@@ -116,11 +130,10 @@
filegroup {
name: "framework-connectivity-tiramisu-sources",
srcs: [
+ ":framework-connectivity-ipsec-sources",
":framework-connectivity-netstats-sources",
":framework-connectivity-nsd-sources",
":framework-connectivity-tiramisu-internal-sources",
],
- visibility: [
- "//frameworks/base",
- ],
+ visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/net/IIpSecService.aidl b/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
similarity index 100%
rename from core/java/android/net/IIpSecService.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
diff --git a/core/java/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
similarity index 100%
rename from core/java/android/net/IpSecAlgorithm.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
diff --git a/core/java/android/net/IpSecConfig.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
similarity index 100%
rename from core/java/android/net/IpSecConfig.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
diff --git a/core/java/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
similarity index 100%
rename from core/java/android/net/IpSecConfig.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
diff --git a/core/java/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
similarity index 100%
rename from core/java/android/net/IpSecManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
diff --git a/core/java/android/net/IpSecSpiResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
diff --git a/core/java/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
similarity index 100%
rename from core/java/android/net/IpSecTransform.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTransformResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
diff --git a/core/java/android/net/IpSecTransformResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
similarity index 100%
rename from core/java/android/net/IpSecTransformResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
similarity index 100%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecUdpEncapResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
similarity index 100%
rename from core/java/android/net/IpSecUdpEncapResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 6a64910..7b88176 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -48,16 +48,28 @@
],
}
+// IpSec related libraries.
+
+filegroup {
+ name: "services.connectivity-ipsec-sources",
+ srcs: [
+ "src/com/android/server/IpSecService.java",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
// Connectivity-T common libraries.
filegroup {
name: "services.connectivity-tiramisu-sources",
srcs: [
+ ":services.connectivity-ipsec-sources",
":services.connectivity-netstats-sources",
":services.connectivity-nsd-sources",
],
path: "src",
- visibility: [
- "//frameworks/base/services/core",
- ],
-}
\ No newline at end of file
+ visibility: ["//frameworks/base/services/core"],
+}
diff --git a/services/core/java/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
similarity index 100%
rename from services/core/java/com/android/server/IpSecService.java
rename to packages/ConnectivityT/service/src/com/android/server/IpSecService.java
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 23c53a1..f60bc97 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -165,6 +165,7 @@
<uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
<uses-permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE" />
<uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" />
+ <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<uses-permission android:name="android.permission.MOVE_PACKAGE" />
<uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
index 1ec6a39..376fea2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.taptotransfer
+import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
@@ -28,6 +29,8 @@
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+const val TAG = "MediaTapToTransfer"
+
/**
* A controller to display and hide the Media Tap-To-Transfer chip. This chip is shown when a user
* is currently playing media on a local "media cast sender" device (e.g. a phone) and gets close
@@ -40,14 +43,14 @@
private val windowManager: WindowManager,
) {
+ @SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
private val windowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
+ flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = "Media Tap-To-Transfer Chip View"
- flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -76,10 +79,20 @@
if (showLoading) { View.VISIBLE } else { View.GONE }
// Undo
- val showUndo = chipState is TransferSucceeded
- currentChipView.requireViewById<View>(R.id.undo).visibility =
- if (showUndo) { View.VISIBLE } else { View.GONE }
+ val undoClickListener: View.OnClickListener? =
+ if (chipState is TransferSucceeded && chipState.undoRunnable != null)
+ View.OnClickListener { chipState.undoRunnable.run() }
+ else
+ null
+ val undoView = currentChipView.requireViewById<View>(R.id.undo)
+ undoView.visibility = if (undoClickListener != null) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ undoView.setOnClickListener(undoClickListener)
+ // Add view if necessary
if (oldChipView == null) {
windowManager.addView(chipView, windowLayoutParams)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
index 05b9552..3b3adfd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
@@ -50,7 +50,11 @@
/**
* A state representing that a transfer has been successfully completed.
+ *
+ * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will
+ * show an Undo button on the chip if this runnable is present.
*/
class TransferSucceeded(
- otherDeviceName: String
+ otherDeviceName: String,
+ val undoRunnable: Runnable? = null
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 9f05c92..6a02dab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.taptotransfer
+import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
@@ -48,7 +49,9 @@
mediaTttChipController.displayChip(TransferInitiated(otherDeviceName))
}
TRANSFER_SUCCEEDED_COMMAND_NAME -> {
- mediaTttChipController.displayChip(TransferSucceeded(otherDeviceName))
+ mediaTttChipController.displayChip(
+ TransferSucceeded(otherDeviceName, fakeUndoRunnable)
+ )
}
else -> {
pw.println("Chip type must be one of " +
@@ -75,6 +78,10 @@
pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
}
}
+
+ private val fakeUndoRunnable = Runnable {
+ Log.i(TAG, "Undo runnable triggered")
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 55d549d..d65fa3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -18,6 +18,7 @@
import android.app.Fragment
import com.android.systemui.R
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent
@@ -50,12 +51,11 @@
override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
.statusBarFragmentComponent ?: throw IllegalStateException()
- val statusBarView = statusBarFragmentComponent.phoneStatusBarView
- val sbViewController =
- statusBarFragmentComponent.phoneStatusBarViewController
-
- statusBarViewUpdatedListener
- ?.onStatusBarViewUpdated(statusBarView, sbViewController)
+ statusBarViewUpdatedListener?.onStatusBarViewUpdated(
+ statusBarFragmentComponent.phoneStatusBarView,
+ statusBarFragmentComponent.phoneStatusBarViewController,
+ statusBarFragmentComponent.phoneStatusBarTransitions
+ )
}
override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
@@ -72,7 +72,8 @@
interface OnStatusBarViewUpdatedListener {
fun onStatusBarViewUpdated(
statusBarView: PhoneStatusBarView,
- statusBarViewController: PhoneStatusBarViewController
+ statusBarViewController: PhoneStatusBarViewController,
+ statusBarTransitions: PhoneStatusBarTransitions
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e1dbf4e..518788b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -669,6 +669,7 @@
public void setIsRemoteInputActive(boolean isActive) {
mIsRemoteInputActive = isActive;
+ updateFooter();
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 55f1450..81d4bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -98,7 +98,6 @@
private boolean mExpandAnimationRunning;
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarView mStatusBarView;
- private PhoneStatusBarTransitions mBarTransitions;
private StatusBar mService;
private NotificationShadeWindowController mNotificationShadeWindowController;
private DragDownHelper mDragDownHelper;
@@ -497,17 +496,8 @@
}
}
- public PhoneStatusBarTransitions getBarTransitions() {
- return mBarTransitions;
- }
-
public void setStatusBarView(PhoneStatusBarView statusBarView) {
mStatusBarView = statusBarView;
- if (statusBarView != null) {
- mBarTransitions = new PhoneStatusBarTransitions(
- statusBarView,
- mStatusBarWindowController.getBackgroundView());
- }
}
public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 3312996..1ca297a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -458,6 +458,7 @@
protected NotificationShadeWindowView mNotificationShadeWindowView;
protected PhoneStatusBarView mStatusBarView;
private PhoneStatusBarViewController mPhoneStatusBarViewController;
+ private PhoneStatusBarTransitions mStatusBarTransitions;
private AuthRippleController mAuthRippleController;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
protected NotificationShadeWindowController mNotificationShadeWindowController;
@@ -1124,9 +1125,10 @@
// Set up CollapsedStatusBarFragment and PhoneStatusBarView
StatusBarInitializer initializer = mStatusBarComponent.getStatusBarInitializer();
initializer.setStatusBarViewUpdatedListener(
- (statusBarView, statusBarViewController) -> {
+ (statusBarView, statusBarViewController, statusBarTransitions) -> {
mStatusBarView = statusBarView;
mPhoneStatusBarViewController = statusBarViewController;
+ mStatusBarTransitions = statusBarTransitions;
mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
// Ensure we re-propagate panel expansion values to the panel controller and
// any listeners it may have, such as PanelBar. This will also ensure we
@@ -2185,14 +2187,10 @@
}, false, sUiEventLogger).show(animationDelay);
}
- protected BarTransitions getStatusBarTransitions() {
- return mNotificationShadeWindowViewController.getBarTransitions();
- }
-
public void checkBarModes() {
if (mDemoModeController.isInDemoMode()) return;
- if (mNotificationShadeWindowViewController != null && getStatusBarTransitions() != null) {
- checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions());
+ if (mStatusBarTransitions != null) {
+ checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions);
}
mNavigationBarController.checkNavBarModes(mDisplayId);
mNoAnimationOnNextBarModeChange = false;
@@ -2219,9 +2217,8 @@
}
private void finishBarAnimations() {
- if (mNotificationShadeWindowController != null
- && mNotificationShadeWindowViewController.getBarTransitions() != null) {
- mNotificationShadeWindowViewController.getBarTransitions().finishAnimations();
+ if (mStatusBarTransitions != null) {
+ mStatusBarTransitions.finishAnimations();
}
mNavigationBarController.finishBarAnimations(mDisplayId);
}
@@ -2275,8 +2272,7 @@
pw.println(" ShadeWindowView: ");
if (mNotificationShadeWindowViewController != null) {
mNotificationShadeWindowViewController.dump(fd, pw, args);
- dumpBarTransitions(pw, "PhoneStatusBarTransitions",
- mNotificationShadeWindowViewController.getBarTransitions());
+ dumpBarTransitions(pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
}
pw.println(" mMediaManager: ");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
index 3c09b78..29c1372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
@@ -32,7 +32,6 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.util.ViewController;
@@ -54,8 +53,7 @@
private final Clock mClockView;
private final View mOperatorNameView;
private final DemoModeController mDemoModeController;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
@@ -64,16 +62,14 @@
Clock clockView,
@Named(OPERATOR_NAME_VIEW) View operatorNameView,
DemoModeController demoModeController,
- NotificationShadeWindowController notificationShadeWindowController,
- NotificationShadeWindowViewController notificationShadeWindowViewController,
+ PhoneStatusBarTransitions phoneStatusBarTransitions,
NavigationBarController navigationBarController,
@DisplayId int displayId) {
super(clockView);
mClockView = clockView;
mOperatorNameView = operatorNameView;
mDemoModeController = demoModeController;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mNotificationShadeWindowViewController = notificationShadeWindowViewController;
+ mPhoneStatusBarTransitions = phoneStatusBarTransitions;
mNavigationBarController = navigationBarController;
mDisplayId = displayId;
}
@@ -128,11 +124,7 @@
-1;
if (barMode != -1) {
boolean animate = true;
- if (mNotificationShadeWindowController != null
- && mNotificationShadeWindowViewController.getBarTransitions() != null) {
- mNotificationShadeWindowViewController.getBarTransitions().transitionTo(
- barMode, animate);
- }
+ mPhoneStatusBarTransitions.transitionTo(barMode, animate);
mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a4aeae9..875b7e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -410,7 +410,7 @@
* dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showGenericBouncer(boolean scrimmed) {
- if (mAlternateAuthInterceptor != null) {
+ if (shouldShowAltAuth()) {
updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
return;
}
@@ -418,6 +418,11 @@
showBouncer(scrimmed);
}
+ private boolean shouldShowAltAuth() {
+ return mAlternateAuthInterceptor != null
+ && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
+ }
+
/**
* Hides the input bouncer (pin/password/pattern).
*/
@@ -473,7 +478,7 @@
// If there is an an alternate auth interceptor (like the UDFPS), show that one instead
// of the bouncer.
- if (mAlternateAuthInterceptor != null) {
+ if (shouldShowAltAuth()) {
if (!afterKeyguardGone) {
mBouncer.setDismissAction(mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index a4ebab9..22b7f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarDemoMode;
@@ -36,6 +37,9 @@
* controllers need access to that view, so those controllers will be re-created whenever the
* fragment is recreated.
*
+ * Anything that depends on {@link CollapsedStatusBarFragment} or {@link PhoneStatusBarView}
+ * should be included here or in {@link StatusBarFragmentModule}.
+ *
* Note that this is completely separate from
* {@link com.android.systemui.statusbar.phone.dagger.StatusBarComponent}. This component gets
* re-created on each new fragment creation, whereas
@@ -90,4 +94,8 @@
/** */
@StatusBarFragmentScope
StatusBarDemoMode getStatusBarDemoMode();
+
+ /** */
+ @StatusBarFragmentScope
+ PhoneStatusBarTransitions getPhoneStatusBarTransitions();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 0cbd401..dea1b43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -22,10 +22,12 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
import javax.inject.Named;
@@ -89,4 +91,14 @@
phoneStatusBarView,
notificationPanelViewController.getStatusBarTouchEventHandler());
}
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
+ static PhoneStatusBarTransitions providePhoneStatusBarTransitions(
+ @RootView PhoneStatusBarView view,
+ StatusBarWindowController statusBarWindowController
+ ) {
+ return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
index 50d94dd..bc0cff1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
@@ -89,7 +89,7 @@
val chipView = getChipView()
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
}
@Test
@@ -99,17 +99,39 @@
val chipView = getChipView()
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferSucceeded_chipTextContainsDeviceName_noLoadingIcon_undo() {
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
+ fun transferSucceededNullUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+ mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, undoRunnable = null))
val chipView = getChipView()
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferSucceededWithUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_undoWithClick() {
+ mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
+
+ val chipView = getChipView()
+ assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() {
+ var runnableRun = false
+ val runnable = Runnable { runnableRun = true }
+
+ mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, runnable))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(runnableRun).isTrue()
}
@Test
@@ -131,9 +153,9 @@
@Test
fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME))
- mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
+ mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
- assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -141,7 +163,7 @@
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
- assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.GONE)
+ assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
private fun LinearLayout.getChipText(): String =
@@ -150,8 +172,7 @@
private fun LinearLayout.getLoadingIconVisibility(): Int =
this.requireViewById<View>(R.id.loading).visibility
- private fun LinearLayout.getUndoButtonVisibility(): Int =
- this.requireViewById<View>(R.id.undo).visibility
+ private fun LinearLayout.getUndoButton(): View = this.requireViewById(R.id.undo)
private fun getChipView(): LinearLayout {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index cbaa460..5d80bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -375,6 +375,39 @@
}
@Test
+ public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
+ // GIVEN alt auth exists, unlocking with biometric isn't allowed
+ mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+ when(mBouncer.isShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false);
+
+ // WHEN showGenericBouncer is called
+ final boolean scrimmed = true;
+ mStatusBarKeyguardViewManager.showGenericBouncer(scrimmed);
+
+ // THEN regular bouncer is shown
+ verify(mBouncer).show(anyBoolean(), eq(scrimmed));
+ verify(mAlternateAuthInterceptor, never()).showAlternateAuthBouncer();
+ }
+
+ @Test
+ public void testShowAltAuth_unlockingWithBiometricAllowed() {
+ // GIVEN alt auth exists, unlocking with biometric is allowed
+ mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+ when(mBouncer.isShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true);
+
+ // WHEN showGenericBouncer is called
+ mStatusBarKeyguardViewManager.showGenericBouncer(true);
+
+ // THEN alt auth bouncer is shown
+ verify(mAlternateAuthInterceptor).showAlternateAuthBouncer();
+ verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
+ }
+
+ @Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 71ae5eb..c7943c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -116,7 +116,6 @@
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleIconFactory;
import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.Bubbles;
@@ -631,7 +630,7 @@
}
@Test
- public void testRemoveLastExpanded_selectsOverflow() {
+ public void testRemoveLastExpanded_collapses() {
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow);
mEntryListener.onPendingEntryAdded(mRow2);
@@ -670,11 +669,10 @@
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_USER_GESTURE);
- // Overflow should be selected
- assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
- assertTrue(mBubbleController.hasBubbles());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+ // We should be collapsed
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
+ assertFalse(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 7b9e6a9..65c219c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -19,8 +19,11 @@
import static android.app.Notification.FLAG_BUBBLE;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -97,7 +100,6 @@
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
@@ -572,7 +574,7 @@
}
@Test
- public void testRemoveLastExpanded_selectsOverflow() {
+ public void testRemoveLastExpanded_collapses() {
// Mark it as a bubble and add it explicitly
mEntryListener.onEntryAdded(mRow);
mEntryListener.onEntryAdded(mRow2);
@@ -611,11 +613,10 @@
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_USER_GESTURE);
- // Overflow should be selected
- assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
- assertTrue(mBubbleController.hasBubbles());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+ // We should be collapsed
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
+ assertFalse(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 0855b9d..0fe90b1 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -6,7 +6,6 @@
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
@@ -22,6 +21,7 @@
import android.os.SELinux;
import android.util.Slog;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.fullbackup.AppMetadataBackupWriter;
import com.android.server.backup.remote.ServiceBackupCallback;
import com.android.server.backup.utils.FullBackupUtils;
@@ -162,7 +162,7 @@
long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis();
try {
mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
- OP_TYPE_BACKUP_WAIT);
+ OpType.BACKUP_WAIT);
IBackupCallback callback =
new ServiceBackupCallback(
@@ -262,7 +262,7 @@
pipes = ParcelFileDescriptor.createPipe();
mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
- OP_TYPE_BACKUP_WAIT);
+ OpType.BACKUP_WAIT);
// We will have to create a runnable that will read the manifest and backup data we
// created, such that we can pipe the data into mOutput. The reason we do this is that
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 98ea03e..81d6381 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -107,12 +107,14 @@
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.fullbackup.FullBackupEntry;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.LifecycleOperationStorage;
import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
import com.android.server.backup.internal.RunInitializeReceiver;
import com.android.server.backup.internal.SetupObserver;
@@ -287,21 +289,6 @@
private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
- // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
- // pending operations list.
- public static final int OP_PENDING = 0;
- private static final int OP_ACKNOWLEDGED = 1;
- private static final int OP_TIMEOUT = -1;
-
- // Waiting for backup agent to respond during backup operation.
- public static final int OP_TYPE_BACKUP_WAIT = 0;
-
- // Waiting for backup agent to respond during restore operation.
- public static final int OP_TYPE_RESTORE_WAIT = 1;
-
- // An entire backup operation spanning multiple packages.
- public static final int OP_TYPE_BACKUP = 2;
-
// Time delay for initialization operations that can be delayed so as not to consume too much
// CPU on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -400,30 +387,8 @@
private ActiveRestoreSession mActiveRestoreSession;
- /**
- * mCurrentOperations contains the list of currently active operations.
- *
- * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
- * An operation wraps a BackupRestoreTask within it.
- * It's the responsibility of this task to remove the operation from this array.
- *
- * A BackupRestore task gets notified of ack/timeout for the operation via
- * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
- * on the mCurrentOpLock.
- * {@link UserBackupManagerService#waitUntilOperationComplete(int)} is
- * used in various places to 'wait' for notifyAll and detect change of pending state of an
- * operation. So typically, an operation will be removed from this array by:
- * - BackupRestoreTask#handleCancel and
- * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
- * these places because waitUntilOperationComplete relies on the operation being present to
- * determine its completion status.
- *
- * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
- * cancel backup tasks.
- */
- @GuardedBy("mCurrentOpLock")
- private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
- private final Object mCurrentOpLock = new Object();
+ private final LifecycleOperationStorage mOperationStorage;
+
private final Random mTokenGenerator = new Random();
private final AtomicInteger mNextToken = new AtomicInteger();
@@ -542,12 +507,14 @@
}
@VisibleForTesting
- UserBackupManagerService(Context context, PackageManager packageManager) {
+ UserBackupManagerService(Context context, PackageManager packageManager,
+ LifecycleOperationStorage operationStorage) {
mContext = context;
mUserId = 0;
mRegisterTransportsRequestedTime = 0;
mPackageManager = packageManager;
+ mOperationStorage = operationStorage;
mBaseStateDir = null;
mDataDir = null;
@@ -600,8 +567,10 @@
BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
mAgentTimeoutParameters.start();
+ mOperationStorage = new LifecycleOperationStorage(mUserId);
+
Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null");
- mBackupHandler = new BackupHandler(this, userBackupThread);
+ mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread);
// Set up our bookkeeping
final ContentResolver resolver = context.getContentResolver();
@@ -756,6 +725,10 @@
return mTransportManager;
}
+ public OperationStorage getOperationStorage() {
+ return mOperationStorage;
+ }
+
public boolean isEnabled() {
return mEnabled;
}
@@ -838,14 +811,6 @@
return mActiveRestoreSession;
}
- public SparseArray<Operation> getCurrentOperations() {
- return mCurrentOperations;
- }
-
- public Object getCurrentOpLock() {
- return mCurrentOpLock;
- }
-
public SparseArray<AdbParams> getAdbBackupRestoreConfirmations() {
return mAdbBackupRestoreConfirmations;
}
@@ -1987,18 +1952,12 @@
}
final long oldToken = Binder.clearCallingIdentity();
try {
- List<Integer> operationsToCancel = new ArrayList<>();
- synchronized (mCurrentOpLock) {
- for (int i = 0; i < mCurrentOperations.size(); i++) {
- Operation op = mCurrentOperations.valueAt(i);
- int token = mCurrentOperations.keyAt(i);
- if (op.type == OP_TYPE_BACKUP) {
- operationsToCancel.add(token);
- }
- }
- }
+ Set<Integer> operationsToCancel =
+ mOperationStorage.operationTokensForOpType(OpType.BACKUP);
+
for (Integer token : operationsToCancel) {
- handleCancel(token, true /* cancelAll */);
+ mOperationStorage.cancelOperation(token, /* cancelAll */ true,
+ operationType -> { /* no callback needed here */ });
}
// We don't want the backup jobs to kick in any time soon.
// Reschedules them to run in the distant future.
@@ -2012,7 +1971,7 @@
/** Schedule a timeout message for the operation identified by {@code token}. */
public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
int operationType) {
- if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
+ if (operationType != OpType.BACKUP_WAIT && operationType != OpType.RESTORE_WAIT) {
Slog.wtf(
TAG,
addUserIdToLogMessage(
@@ -2036,19 +1995,17 @@
+ callback));
}
- synchronized (mCurrentOpLock) {
- mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
- Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
- token, 0, callback);
- mBackupHandler.sendMessageDelayed(msg, interval);
- }
+ mOperationStorage.registerOperation(token, OpState.PENDING, callback, operationType);
+ Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
+ token, 0, callback);
+ mBackupHandler.sendMessageDelayed(msg, interval);
}
private int getMessageIdForOperationType(int operationType) {
switch (operationType) {
- case OP_TYPE_BACKUP_WAIT:
+ case OpType.BACKUP_WAIT:
return MSG_BACKUP_OPERATION_TIMEOUT;
- case OP_TYPE_RESTORE_WAIT:
+ case OpType.RESTORE_WAIT:
return MSG_RESTORE_OPERATION_TIMEOUT;
default:
Slog.wtf(
@@ -2061,162 +2018,28 @@
}
}
- /**
- * Add an operation to the list of currently running operations. Used for cancellation,
- * completion and timeout callbacks that act on the operation via the {@code token}.
- */
- public void putOperation(int token, Operation operation) {
- if (MORE_DEBUG) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Adding operation token="
- + Integer.toHexString(token)
- + ", operation type="
- + operation.type));
- }
- synchronized (mCurrentOpLock) {
- mCurrentOperations.put(token, operation);
- }
- }
-
- /**
- * Remove an operation from the list of currently running operations. An operation is removed
- * when it is completed, cancelled, or timed out, and thus no longer running.
- */
- public void removeOperation(int token) {
- if (MORE_DEBUG) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId, "Removing operation token=" + Integer.toHexString(token)));
- }
- synchronized (mCurrentOpLock) {
- if (mCurrentOperations.get(token) == null) {
- Slog.w(TAG, addUserIdToLogMessage(mUserId, "Duplicate remove for operation. token="
- + Integer.toHexString(token)));
- }
- mCurrentOperations.remove(token);
- }
- }
-
/** Block until we received an operation complete message (from the agent or cancellation). */
public boolean waitUntilOperationComplete(int token) {
- if (MORE_DEBUG) {
- Slog.i(TAG, addUserIdToLogMessage(mUserId, "Blocking until operation complete for "
- + Integer.toHexString(token)));
- }
- int finalState = OP_PENDING;
- Operation op = null;
- synchronized (mCurrentOpLock) {
- while (true) {
- op = mCurrentOperations.get(token);
- if (op == null) {
- // mysterious disappearance: treat as success with no callback
- break;
- } else {
- if (op.state == OP_PENDING) {
- try {
- mCurrentOpLock.wait();
- } catch (InterruptedException e) {
- }
- // When the wait is notified we loop around and recheck the current state
- } else {
- if (MORE_DEBUG) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Unblocked waiting for operation token="
- + Integer.toHexString(token)));
- }
- // No longer pending; we're done
- finalState = op.state;
- break;
- }
- }
- }
- }
-
- removeOperation(token);
- if (op != null) {
- mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
- }
- if (MORE_DEBUG) {
- Slog.v(TAG, addUserIdToLogMessage(mUserId, "operation " + Integer.toHexString(token)
- + " complete: finalState=" + finalState));
- }
- return finalState == OP_ACKNOWLEDGED;
+ return mOperationStorage.waitUntilOperationComplete(token, operationType -> {
+ mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
+ });
}
/** Cancel the operation associated with {@code token}. */
public void handleCancel(int token, boolean cancelAll) {
- // Notify any synchronous waiters
- Operation op = null;
- synchronized (mCurrentOpLock) {
- op = mCurrentOperations.get(token);
- if (MORE_DEBUG) {
- if (op == null) {
- Slog.w(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Cancel of token "
- + Integer.toHexString(token)
- + " but no op found"));
- }
+ // Remove all pending timeout messages of types OpType.BACKUP_WAIT and
+ // OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
+ // doesn't require cancellation.
+ mOperationStorage.cancelOperation(token, cancelAll, operationType -> {
+ if (operationType == OpType.BACKUP_WAIT || operationType == OpType.RESTORE_WAIT) {
+ mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
}
- int state = (op != null) ? op.state : OP_TIMEOUT;
- if (state == OP_ACKNOWLEDGED) {
- // The operation finished cleanly, so we have nothing more to do.
- if (DEBUG) {
- Slog.w(TAG, addUserIdToLogMessage(mUserId, "Operation already got an ack."
- + "Should have been removed from mCurrentOperations."));
- }
- op = null;
- mCurrentOperations.delete(token);
- } else if (state == OP_PENDING) {
- if (DEBUG) {
- Slog.v(
- TAG,
- addUserIdToLogMessage(
- mUserId, "Cancel: token=" + Integer.toHexString(token)));
- }
- op.state = OP_TIMEOUT;
- // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
- // called after we receive cancel here. We need this op's state there.
-
- // Remove all pending timeout messages of types OP_TYPE_BACKUP_WAIT and
- // OP_TYPE_RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
- // doesn't require cancellation.
- if (op.type == OP_TYPE_BACKUP_WAIT || op.type == OP_TYPE_RESTORE_WAIT) {
- mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
- }
- }
- mCurrentOpLock.notifyAll();
- }
-
- // If there's a TimeoutHandler for this event, call it
- if (op != null && op.callback != null) {
- if (MORE_DEBUG) {
- Slog.v(TAG, addUserIdToLogMessage(mUserId, " Invoking cancel on " + op.callback));
- }
- op.callback.handleCancel(cancelAll);
- }
+ });
}
/** Returns {@code true} if a backup is currently running, else returns {@code false}. */
public boolean isBackupOperationInProgress() {
- synchronized (mCurrentOpLock) {
- for (int i = 0; i < mCurrentOperations.size(); i++) {
- Operation op = mCurrentOperations.valueAt(i);
- if (op.type == OP_TYPE_BACKUP && op.state == OP_PENDING) {
- return true;
- }
- }
- }
- return false;
+ return mOperationStorage.isBackupOperationInProgress();
}
/** Unbind the backup agent and kill the app if it's a non-system app. */
@@ -2578,6 +2401,7 @@
String[] pkg = new String[]{entry.packageName};
mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport(
this,
+ mOperationStorage,
/* observer */ null,
pkg,
/* updateSchedule */ true,
@@ -3107,6 +2931,7 @@
CountDownLatch latch = new CountDownLatch(1);
Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport(
this,
+ mOperationStorage,
/* observer */ null,
pkgNames,
/* updateSchedule */ false,
@@ -4126,48 +3951,11 @@
* outstanding asynchronous backup/restore operation.
*/
public void opComplete(int token, long result) {
- if (MORE_DEBUG) {
- Slog.v(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "opComplete: " + Integer.toHexString(token) + " result=" + result));
- }
- Operation op = null;
- synchronized (mCurrentOpLock) {
- op = mCurrentOperations.get(token);
- if (op != null) {
- if (op.state == OP_TIMEOUT) {
- // The operation already timed out, and this is a late response. Tidy up
- // and ignore it; we've already dealt with the timeout.
- op = null;
- mCurrentOperations.delete(token);
- } else if (op.state == OP_ACKNOWLEDGED) {
- if (DEBUG) {
- Slog.w(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Received duplicate ack for token="
- + Integer.toHexString(token)));
- }
- op = null;
- mCurrentOperations.remove(token);
- } else if (op.state == OP_PENDING) {
- // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
- // called after we we receive this call.
- op.state = OP_ACKNOWLEDGED;
- }
- }
- mCurrentOpLock.notifyAll();
- }
-
- // The completion callback, if any, is invoked on the handler
- if (op != null && op.callback != null) {
- Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(op.callback, result);
+ mOperationStorage.onOperationComplete(token, result, callback -> {
+ Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result);
Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult);
mBackupHandler.sendMessage(msg);
- }
+ });
}
/** Checks if the package is eligible for backup. */
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index fe5497f..1e1ca95 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -21,7 +21,6 @@
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import android.annotation.UserIdInt;
@@ -39,6 +38,7 @@
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -147,7 +147,7 @@
mToken,
timeout,
mTimeoutMonitor /* in parent class */,
- OP_TYPE_BACKUP_WAIT);
+ OpType.BACKUP_WAIT);
mAgent.doFullBackup(
mPipe,
mQuota,
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
index aaf1f0a..be6ac26 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
@@ -18,7 +18,6 @@
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
import android.app.backup.IBackupManager;
import android.content.ComponentName;
@@ -33,6 +32,7 @@
import com.android.internal.backup.IObbBackupService;
import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.utils.FullBackupUtils;
@@ -83,7 +83,7 @@
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
backupManagerService.prepareOperationTimeout(
- token, fullBackupAgentTimeoutMillis, null, OP_TYPE_BACKUP_WAIT);
+ token, fullBackupAgentTimeoutMillis, null, OpType.BACKUP_WAIT);
mService.backupObbs(pkg.packageName, pipes[1], token,
backupManagerService.getBackupManagerBinder());
FullBackupUtils.routeSocketDataToOutput(pipes[0], out);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 448e086..7ee307e 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -37,6 +37,7 @@
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.KeyValueAdbBackupEngine;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.PasswordUtils;
@@ -67,6 +68,7 @@
public class PerformAdbBackupTask extends FullBackupTask implements BackupRestoreTask {
private final UserBackupManagerService mUserBackupManagerService;
+ private final OperationStorage mOperationStorage;
private final AtomicBoolean mLatch;
private final ParcelFileDescriptor mOutputFile;
@@ -85,7 +87,8 @@
private final int mCurrentOpToken;
private final BackupEligibilityRules mBackupEligibilityRules;
- public PerformAdbBackupTask(UserBackupManagerService backupManagerService,
+ public PerformAdbBackupTask(
+ UserBackupManagerService backupManagerService, OperationStorage operationStorage,
ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
@@ -93,6 +96,7 @@
BackupEligibilityRules backupEligibilityRules) {
super(observer);
mUserBackupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mLatch = latch;
@@ -505,6 +509,6 @@
if (target != null) {
mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
}
- mUserBackupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 9ce4eab..0ca77d1 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -19,9 +19,6 @@
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
-import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
import android.annotation.Nullable;
import android.app.IBackupAgent;
@@ -45,10 +42,12 @@
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FullBackupJob;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
@@ -99,6 +98,7 @@
public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
public static PerformFullTransportBackupTask newWithCurrentTransport(
UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
IFullBackupRestoreObserver observer,
String[] whichPackages,
boolean updateSchedule,
@@ -118,6 +118,7 @@
listenerCaller);
return new PerformFullTransportBackupTask(
backupManagerService,
+ operationStorage,
transportConnection,
observer,
whichPackages,
@@ -136,6 +137,7 @@
private UserBackupManagerService mUserBackupManagerService;
private final Object mCancelLock = new Object();
+ OperationStorage mOperationStorage;
List<PackageInfo> mPackages;
PackageInfo mCurrentPackage;
boolean mUpdateSchedule;
@@ -158,6 +160,7 @@
private final BackupEligibilityRules mBackupEligibilityRules;
public PerformFullTransportBackupTask(UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
TransportConnection transportConnection,
IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
@@ -165,7 +168,8 @@
@Nullable IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
boolean userInitiated, BackupEligibilityRules backupEligibilityRules) {
super(observer);
- this.mUserBackupManagerService = backupManagerService;
+ mUserBackupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mTransportConnection = transportConnection;
mUpdateSchedule = updateSchedule;
mLatch = latch;
@@ -261,16 +265,13 @@
}
private void registerTask() {
- synchronized (mUserBackupManagerService.getCurrentOpLock()) {
- Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
- mUserBackupManagerService.getCurrentOperations().put(
- mCurrentOpToken,
- new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
- }
+ Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+ mOperationStorage.registerOperation(mCurrentOpToken, OpState.PENDING, this, OpType.BACKUP);
}
+ // public, because called from KeyValueBackupTask.finishTask.
public void unregisterTask() {
- mUserBackupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
@Override
@@ -722,7 +723,7 @@
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
try {
mUserBackupManagerService.prepareOperationTimeout(
- mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
+ mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OpType.BACKUP_WAIT);
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
@@ -777,7 +778,7 @@
}
mResult.set(result);
mLatch.countDown();
- mUserBackupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
@Override
@@ -787,7 +788,7 @@
}
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
- mUserBackupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
@Override
@@ -837,16 +838,12 @@
}
void registerTask() {
- synchronized (mUserBackupManagerService.getCurrentOpLock()) {
- mUserBackupManagerService.getCurrentOperations().put(
- mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT));
- }
+ mOperationStorage.registerOperation(mCurrentOpToken,
+ OpState.PENDING, this, OpType.BACKUP_WAIT);
}
void unregisterTask() {
- synchronized (mUserBackupManagerService.getCurrentOpLock()) {
- mUserBackupManagerService.getCurrentOperations().remove(mCurrentOpToken);
- }
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
@Override
@@ -956,7 +953,7 @@
mPreflightLatch.countDown();
mBackupLatch.countDown();
// We are done with this operation.
- mUserBackupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 5c24859..03796ea 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -35,6 +35,7 @@
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
@@ -84,6 +85,7 @@
public static final int MSG_STOP = 22;
private final UserBackupManagerService backupManagerService;
+ private final OperationStorage mOperationStorage;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final HandlerThread mBackupThread;
@@ -92,10 +94,12 @@
volatile boolean mIsStopping = false;
public BackupHandler(
- UserBackupManagerService backupManagerService, HandlerThread backupThread) {
+ UserBackupManagerService backupManagerService, OperationStorage operationStorage,
+ HandlerThread backupThread) {
super(backupThread.getLooper());
mBackupThread = backupThread;
this.backupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
@@ -215,6 +219,7 @@
caller);
KeyValueBackupTask.start(
backupManagerService,
+ mOperationStorage,
transportConnection,
transport.transportDirName(),
queue,
@@ -278,8 +283,8 @@
// TODO: refactor full backup to be a looper-based state machine
// similar to normal backup/restore.
AdbBackupParams params = (AdbBackupParams) msg.obj;
- PerformAdbBackupTask task = new PerformAdbBackupTask(backupManagerService,
- params.fd,
+ PerformAdbBackupTask task = new PerformAdbBackupTask(
+ backupManagerService, mOperationStorage, params.fd,
params.observer, params.includeApks, params.includeObbs,
params.includeShared, params.doWidgets, params.curPassword,
params.encryptPassword, params.allApps, params.includeSystem,
@@ -296,6 +301,7 @@
PerformUnifiedRestoreTask task =
new PerformUnifiedRestoreTask(
backupManagerService,
+ mOperationStorage,
params.mTransportConnection,
params.observer,
params.monitor,
@@ -332,7 +338,7 @@
// similar to normal backup/restore.
AdbRestoreParams params = (AdbRestoreParams) msg.obj;
PerformAdbRestoreTask task = new PerformAdbRestoreTask(backupManagerService,
- params.fd,
+ mOperationStorage, params.fd,
params.curPassword, params.encryptPassword,
params.observer, params.latch);
(new Thread(task, "adb-restore")).start();
@@ -459,6 +465,7 @@
KeyValueBackupTask.start(
backupManagerService,
+ mOperationStorage,
params.mTransportConnection,
params.dirName,
params.kvPackages,
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 30da8c1..16aa4eb 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -23,8 +23,6 @@
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
-import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -57,10 +55,12 @@
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.remote.RemoteCallable;
import com.android.server.backup.remote.RemoteResult;
@@ -211,6 +211,7 @@
*/
public static KeyValueBackupTask start(
UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
TransportConnection transportConnection,
String transportDirName,
List<String> queue,
@@ -227,6 +228,7 @@
KeyValueBackupTask task =
new KeyValueBackupTask(
backupManagerService,
+ operationStorage,
transportConnection,
transportDirName,
queue,
@@ -244,6 +246,7 @@
}
private final UserBackupManagerService mBackupManagerService;
+ private final OperationStorage mOperationStorage;
private final PackageManager mPackageManager;
private final TransportConnection mTransportConnection;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@@ -302,6 +305,7 @@
@VisibleForTesting
public KeyValueBackupTask(
UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
TransportConnection transportConnection,
String transportDirName,
List<String> queue,
@@ -313,6 +317,7 @@
boolean nonIncremental,
BackupEligibilityRules backupEligibilityRules) {
mBackupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mPackageManager = backupManagerService.getPackageManager();
mTransportConnection = transportConnection;
mOriginalQueue = queue;
@@ -338,12 +343,11 @@
}
private void registerTask() {
- mBackupManagerService.putOperation(
- mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
+ mOperationStorage.registerOperation(mCurrentOpToken, OpState.PENDING, this, OpType.BACKUP);
}
private void unregisterTask() {
- mBackupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
@Override
@@ -639,6 +643,7 @@
private PerformFullTransportBackupTask createFullBackupTask(List<String> packages) {
return new PerformFullTransportBackupTask(
mBackupManagerService,
+ mOperationStorage,
mTransportConnection,
/* fullBackupRestoreObserver */ null,
packages.toArray(new String[packages.size()]),
diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
index 376b618..cfc0f20 100644
--- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
+++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
@@ -23,6 +23,7 @@
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.UserBackupManagerService;
import java.util.Objects;
@@ -36,13 +37,16 @@
private static final String TAG = "AdbRestoreFinishedLatch";
private UserBackupManagerService backupManagerService;
+ private final OperationStorage mOperationStorage;
final CountDownLatch mLatch;
private final int mCurrentOpToken;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
public AdbRestoreFinishedLatch(UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
int currentOpToken) {
this.backupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mLatch = new CountDownLatch(1);
mCurrentOpToken = currentOpToken;
mAgentTimeoutParameters = Objects.requireNonNull(
@@ -72,7 +76,7 @@
Slog.w(TAG, "adb onRestoreFinished() complete");
}
mLatch.countDown();
- backupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
@Override
@@ -81,6 +85,6 @@
Slog.w(TAG, "adb onRestoreFinished() timed out");
}
mLatch.countDown();
- backupManagerService.removeOperation(mCurrentOpToken);
+ mOperationStorage.removeOperation(mCurrentOpToken);
}
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 5718bdf..76df8b9 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -21,7 +21,6 @@
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
@@ -48,6 +47,8 @@
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.KeyValueAdbRestoreEngine;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.FullBackupObbConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -71,6 +72,7 @@
public class FullRestoreEngine extends RestoreEngine {
private final UserBackupManagerService mBackupManagerService;
+ private final OperationStorage mOperationStorage;
private final int mUserId;
// Task in charge of monitoring timeouts
@@ -133,12 +135,14 @@
private boolean mPipesClosed;
private final BackupEligibilityRules mBackupEligibilityRules;
- public FullRestoreEngine(UserBackupManagerService backupManagerService,
+ public FullRestoreEngine(
+ UserBackupManagerService backupManagerService, OperationStorage operationStorage,
BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
int ephemeralOpToken, boolean isAdbRestore,
BackupEligibilityRules backupEligibilityRules) {
mBackupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
mObserver = observer;
@@ -409,7 +413,7 @@
mBackupManagerService.prepareOperationTimeout(token,
timeout,
mMonitorTask,
- OP_TYPE_RESTORE_WAIT);
+ OpType.RESTORE_WAIT);
if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
if (DEBUG) {
@@ -603,9 +607,9 @@
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
- mBackupManagerService, token);
+ mBackupManagerService, mOperationStorage, token);
mBackupManagerService.prepareOperationTimeout(
- token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT);
+ token, fullBackupAgentTimeoutMillis, latch, OpType.RESTORE_WAIT);
if (mTargetApp.processName.equals("system")) {
if (MORE_DEBUG) {
Slog.d(TAG, "system agent - restoreFinished on thread");
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index e03150e..22af19e 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.FullBackupObbConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -60,6 +61,7 @@
public class PerformAdbRestoreTask implements Runnable {
private final UserBackupManagerService mBackupManagerService;
+ private final OperationStorage mOperationStorage;
private final ParcelFileDescriptor mInputFile;
private final String mCurrentPassword;
private final String mDecryptPassword;
@@ -68,10 +70,12 @@
private IFullBackupRestoreObserver mObserver;
- public PerformAdbRestoreTask(UserBackupManagerService backupManagerService,
+ public PerformAdbRestoreTask(
+ UserBackupManagerService backupManagerService, OperationStorage operationStorage,
ParcelFileDescriptor fd, String curPassword, String decryptPassword,
IFullBackupRestoreObserver observer, AtomicBoolean latch) {
this.mBackupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mInputFile = fd;
mCurrentPassword = curPassword;
mDecryptPassword = decryptPassword;
@@ -109,9 +113,9 @@
mBackupManagerService.getPackageManager(),
LocalServices.getService(PackageManagerInternal.class),
mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP);
- FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null,
- mObserver, null, null, true, 0 /*unused*/, true,
- eligibilityRules);
+ FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
+ mOperationStorage, null, mObserver, null, null,
+ true, 0 /*unused*/, true, eligibilityRules);
FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine,
tarInputStream);
mEngineThread.run();
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index ac831af..b48367d 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -20,7 +20,6 @@
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
@@ -60,6 +59,8 @@
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.BackupUtils;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
import com.android.server.backup.TransportManager;
@@ -84,6 +85,7 @@
public class PerformUnifiedRestoreTask implements BackupRestoreTask {
private UserBackupManagerService backupManagerService;
+ private final OperationStorage mOperationStorage;
private final int mUserId;
private final TransportManager mTransportManager;
// Transport client we're working with to do the restore
@@ -169,6 +171,7 @@
PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
mListener = null;
mAgentTimeoutParameters = null;
+ mOperationStorage = null;
mTransportConnection = null;
mTransportManager = null;
mEphemeralOpToken = 0;
@@ -181,6 +184,7 @@
// about releasing it.
public PerformUnifiedRestoreTask(
UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
TransportConnection transportConnection,
IRestoreObserver observer,
IBackupManagerMonitor monitor,
@@ -192,6 +196,7 @@
OnTaskFinishedListener listener,
BackupEligibilityRules backupEligibilityRules) {
this.backupManagerService = backupManagerService;
+ mOperationStorage = operationStorage;
mUserId = backupManagerService.getUserId();
mTransportManager = backupManagerService.getTransportManager();
mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
@@ -767,7 +772,7 @@
long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(
app.applicationInfo.uid);
backupManagerService.prepareOperationTimeout(
- mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
+ mEphemeralOpToken, restoreAgentTimeoutMillis, this, OpType.RESTORE_WAIT);
startedAgentRestore = true;
mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState,
mEphemeralOpToken, backupManagerService.getBackupManagerBinder(),
@@ -877,7 +882,7 @@
backupManagerService
.prepareOperationTimeout(mEphemeralOpToken,
restoreAgentFinishedTimeoutMillis, this,
- OP_TYPE_RESTORE_WAIT);
+ OpType.RESTORE_WAIT);
mAgent.doRestoreFinished(mEphemeralOpToken,
backupManagerService.getBackupManagerBinder());
// If we get this far, the callback or timeout will schedule the
@@ -921,7 +926,7 @@
EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
mCurrentPackage.packageName);
- mEngine = new FullRestoreEngine(backupManagerService, this, null,
+ mEngine = new FullRestoreEngine(backupManagerService, mOperationStorage, this, null,
mMonitor, mCurrentPackage, false, mEphemeralOpToken, false,
mBackupEligibilityRules);
mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
@@ -1071,7 +1076,7 @@
// The app has timed out handling a restoring file
@Override
public void handleCancel(boolean cancelAll) {
- backupManagerService.removeOperation(mEphemeralOpToken);
+ mOperationStorage.removeOperation(mEphemeralOpToken);
if (DEBUG) {
Slog.w(TAG, "Full-data restore target timed out; shutting down");
}
@@ -1268,7 +1273,7 @@
@Override
public void operationComplete(long unusedResult) {
- backupManagerService.removeOperation(mEphemeralOpToken);
+ mOperationStorage.removeOperation(mEphemeralOpToken);
if (MORE_DEBUG) {
Slog.i(TAG, "operationComplete() during restore: target="
+ mCurrentPackage.packageName
@@ -1331,7 +1336,7 @@
// A call to agent.doRestore() or agent.doRestoreFinished() has timed out
@Override
public void handleCancel(boolean cancelAll) {
- backupManagerService.removeOperation(mEphemeralOpToken);
+ mOperationStorage.removeOperation(mEphemeralOpToken);
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index bcc345f..637994f 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -16,8 +16,13 @@
package com.android.server.companion;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
+import static android.content.ComponentName.createRelative;
+
import static com.android.internal.util.CollectionUtils.filter;
-import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
@@ -28,73 +33,114 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
-import android.companion.CompanionDeviceManager;
import android.companion.IAssociationRequestCallback;
-import android.companion.ICompanionDeviceDiscoveryService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
+import android.net.MacAddress;
import android.os.Binder;
-import android.os.IBinder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.util.PackageUtils;
import android.util.Slog;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.PerUser;
-import com.android.internal.infra.ServiceConnector;
import com.android.internal.util.ArrayUtils;
-import com.android.server.FgThread;
-import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.Objects;
import java.util.Set;
+/**
+ * Class responsible for handling incoming {@link AssociationRequest}s.
+ * The main responsibilities of an {@link AssociationRequestsProcessor} are:
+ * <ul>
+ * <li> Requests validation and checking if the package that would own the association holds all
+ * necessary permissions.
+ * <li> Communication with the requester via a provided
+ * {@link android.companion.CompanionDeviceManager.Callback}.
+ * <li> Constructing an {@link Intent} for collecting user's approval (if needed), and handling the
+ * approval.
+ * <li> Calling to {@link CompanionDeviceManagerService} to create an association when/if the
+ * request was found valid and was approved by user.
+ * </ul>
+ *
+ * The class supports two variants of the "Association Flow": the full variant, and the shortened
+ * (a.k.a. No-UI) variant.
+ * Both flows start similarly: in
+ * {@link #processNewAssociationRequest(AssociationRequest, String, int, IAssociationRequestCallback)}
+ * invoked from
+ * {@link CompanionDeviceManagerService.CompanionDeviceManagerImpl#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
+ * method call.
+ * Then an {@link AssociationRequestsProcessor} makes a decision whether user's confirmation is
+ * required.
+ *
+ * If the user's approval is NOT required: an {@link AssociationRequestsProcessor} invokes
+ * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback)}
+ * which after calling to {@link CompanionDeviceManagerService} to create an association, notifies
+ * the requester via
+ * {@link android.companion.CompanionDeviceManager.Callback#onAssociationCreated(AssociationInfo)}.
+ *
+ * If the user's approval is required: an {@link AssociationRequestsProcessor} constructs a
+ * {@link PendingIntent} for the approval UI and sends it back to the requester via
+ * {@link android.companion.CompanionDeviceManager.Callback#onAssociationPending(IntentSender)}.
+ * When/if user approves the request, {@link AssociationRequestsProcessor} receives a "callback"
+ * from the Approval UI in via {@link #mOnRequestConfirmationReceiver} and invokes
+ * {@link #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback, ResultReceiver, MacAddress)}
+ * which one more time checks that the packages holds all necessary permissions before proceeding to
+ * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback)}.
+ *
+ * @see #processNewAssociationRequest(AssociationRequest, String, int, IAssociationRequestCallback)
+ * @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback,
+ * ResultReceiver, MacAddress)
+ */
class AssociationRequestsProcessor {
private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
- private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
- CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
- ".CompanionDeviceDiscoveryService");
+ private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
+ createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
+
+ // AssociationRequestsProcessor <-> UI
+ private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
+ private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
+ private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
+
+ // AssociationRequestsProcessor -> UI
+ private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
+ private static final String EXTRA_ASSOCIATION = "association";
+
+ // UI -> AssociationRequestsProcessor
+ private static final int RESULT_CODE_ASSOCIATION_APPROVED = 0;
+ private static final String EXTRA_MAC_ADDRESS = "mac_address";
private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
private final Context mContext;
private final CompanionDeviceManagerService mService;
- private final PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
-
- private AssociationRequest mRequest;
- private IAssociationRequestCallback mAppCallback;
- private AndroidFuture<?> mOngoingDeviceDiscovery;
+ private final PackageManagerInternal mPackageManager;
AssociationRequestsProcessor(CompanionDeviceManagerService service) {
mContext = service.getContext();
mService = service;
-
- final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
- mServiceConnectors = new PerUser<>() {
- @Override
- protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) {
- return new ServiceConnector.Impl<>(
- mContext,
- serviceIntent, 0/* bindingFlags */, userId,
- ICompanionDeviceDiscoveryService.Stub::asInterface);
- }
- };
+ mPackageManager = service.mPackageManagerInternal;
}
/**
* Handle incoming {@link AssociationRequest}s, sent via
* {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
*/
- void process(@NonNull AssociationRequest request, @NonNull String packageName,
- @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) {
+ void processNewAssociationRequest(@NonNull AssociationRequest request,
+ @NonNull String packageName, @UserIdInt int userId,
+ @NonNull IAssociationRequestCallback callback) {
requireNonNull(request, "Request MUST NOT be null");
if (request.isSelfManaged()) {
requireNonNull(request.getDisplayName(), "AssociationRequest.displayName "
@@ -103,14 +149,15 @@
requireNonNull(packageName, "Package name MUST NOT be null");
requireNonNull(callback, "Callback MUST NOT be null");
+ final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
if (DEBUG) {
- Slog.d(TAG, "process() "
+ Slog.d(TAG, "processNewAssociationRequest() "
+ "request=" + request + ", "
- + "package=u" + userId + "/" + packageName);
+ + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")");
}
// 1. Enforce permissions and other requirements.
- enforcePermissionsForAssociation(mContext, request, packageName, userId);
+ enforcePermissionsForAssociation(mContext, request, packageUid);
mService.checkUsesFeature(packageName, userId);
// 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
@@ -118,71 +165,99 @@
if (request.isSelfManaged() && !request.isForceConfirmation()
&& !willAddRoleHolder(request, packageName, userId)) {
// 2a. Create association right away.
- final AssociationInfo association = mService.createAssociation(userId, packageName,
- /* macAddress */ null, request.getDisplayName(), request.getDeviceProfile(),
- /* selfManaged */true);
- withCatchingRemoteException(() -> callback.onAssociationCreated(association));
+ createAssociationAndNotifyApplication(request, packageName, userId,
+ /*macAddress*/ null, callback);
return;
}
- // 2b. Launch the UI.
- synchronized (mService.mLock) {
- if (mRequest != null) {
- Slog.w(TAG, "CDM is already processing another AssociationRequest.");
+ // 2b. Build a PendingIntent for launching the confirmation UI, and send it back to the app:
- withCatchingRemoteException(() -> callback.onFailure("Busy."));
- }
+ // 2b.1. Populate the request with required info.
+ request.setPackageName(packageName);
+ request.setUserId(userId);
+ request.setSkipPrompt(mayAssociateWithoutPrompt(request, packageName, userId));
- final boolean linked = withCatchingRemoteException(
- () -> callback.asBinder().linkToDeath(mBinderDeathRecipient, 0));
- if (!linked) {
- // The process has died by now: do not proceed.
- return;
- }
+ // 2b.2. Prepare extras and create an Intent.
+ final Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_ASSOCIATION_REQUEST, request);
+ extras.putBinder(EXTRA_APPLICATION_CALLBACK, callback.asBinder());
+ extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
- mRequest = request;
+ final Intent intent = new Intent();
+ intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+ intent.putExtras(extras);
+
+ // 2b.3. Create a PendingIntent.
+ final PendingIntent pendingIntent;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Using uid of the application that will own the association (usually the same
+ // application that sent the request) allows us to have multiple "pending" association
+ // requests at the same time.
+ // If the application already has a pending association request, that PendingIntent
+ // will be cancelled.
+ pendingIntent = PendingIntent.getActivity(mContext, /*requestCode */ packageUid, intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- mAppCallback = callback;
- request.setCallingPackage(packageName);
+ // 2b.4. Send the PendingIntent back to the app.
+ try {
+ callback.onAssociationPending(pendingIntent);
+ } catch (RemoteException ignore) { }
+ }
- if (mayAssociateWithoutPrompt(packageName, userId)) {
- Slog.i(TAG, "setSkipPrompt(true)");
- request.setSkipPrompt(true);
+ private void processAssociationRequestApproval(@NonNull AssociationRequest request,
+ @NonNull IAssociationRequestCallback callback,
+ @NonNull ResultReceiver resultReceiver, @Nullable MacAddress macAddress) {
+ final String packageName = request.getPackageName();
+ final int userId = request.getUserId();
+ final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
+
+ if (DEBUG) {
+ Slog.d(TAG, "processAssociationRequestApproval()\n"
+ + " package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n"
+ + " request=" + request + "\n"
+ + " macAddress=" + macAddress + "\n");
}
- final String deviceProfile = request.getDeviceProfile();
- mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile)
- .thenComposeAsync(description -> {
- if (DEBUG) {
- Slog.d(TAG, "fetchProfileDescription done: " + description);
- }
+ // 1. Need to check permissions again in case something changed, since we first received
+ // this request.
+ try {
+ enforcePermissionsForAssociation(mContext, request, packageUid);
+ } catch (SecurityException e) {
+ // Since, at this point the caller is our own UI, we need to catch the exception on
+ // forward it back to the application via the callback.
+ try {
+ callback.onFailure(e.getMessage());
+ } catch (RemoteException ignore) { }
+ return;
+ }
- request.setDeviceProfilePrivilegesDescription(description);
+ // 2. Create association and notify the application.
+ final AssociationInfo association = createAssociationAndNotifyApplication(
+ request, packageName, userId, macAddress, callback);
- return mServiceConnectors.forUser(userId).postAsync(service -> {
- if (DEBUG) {
- Slog.d(TAG, "Connected to CDM service -> "
- + "Starting discovery for " + request);
- }
+ // 3. Send the association back the Approval Activity, so that it can report back to the app
+ // via Activity.setResult().
+ final Bundle data = new Bundle();
+ data.putParcelable(EXTRA_ASSOCIATION, association);
+ resultReceiver.send(RESULT_CODE_ASSOCIATION_CREATED, data);
+ }
- AndroidFuture<String> future = new AndroidFuture<>();
- service.startDiscovery(request, packageName, callback, future);
- return future;
- }).cancelTimeout();
+ private AssociationInfo createAssociationAndNotifyApplication(
+ @NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
+ @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) {
+ final AssociationInfo association = mService.createAssociation(userId, packageName,
+ macAddress, request.getDisplayName(), request.getDeviceProfile(),
+ request.isSelfManaged());
- }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
- if (err == null) {
- mService.legacyCreateAssociation(
- userId, deviceAddress, packageName, deviceProfile);
- mServiceConnectors.forUser(userId).post(
- ICompanionDeviceDiscoveryService::onAssociationCreated);
- } else {
- Slog.e(TAG, "Failed to discover device(s)", err);
- callback.onFailure("No devices found: " + err.getMessage());
- }
- cleanup();
- }));
+ try {
+ callback.onAssociationCreated(association);
+ } catch (RemoteException ignore) { }
+
+ return association;
}
private boolean willAddRoleHolder(@NonNull AssociationRequest request,
@@ -197,26 +272,44 @@
return !isRoleHolder;
}
- private void cleanup() {
- if (DEBUG) {
- Slog.d(TAG, "cleanup(); discovery = "
- + mOngoingDeviceDiscovery + ", request = " + mRequest);
- }
- synchronized (mService.mLock) {
- AndroidFuture<?> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
- if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
- ongoingDeviceDiscovery.cancel(true);
+ private final ResultReceiver mOnRequestConfirmationReceiver =
+ new ResultReceiver(Handler.getMain()) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle data) {
+ if (DEBUG) {
+ Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() "
+ + "code=" + resultCode + ", " + "data=" + data);
}
- if (mAppCallback != null) {
- mAppCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
- mAppCallback = null;
- }
- mRequest = null;
- }
- }
- private boolean mayAssociateWithoutPrompt(String packageName, int userId) {
- final String deviceProfile = mRequest.getDeviceProfile();
+ if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
+ Slog.w(TAG, "Unknown result code:" + resultCode);
+ return;
+ }
+
+ final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST);
+ final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
+ .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
+ final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER);
+
+ requireNonNull(request);
+ requireNonNull(callback);
+ requireNonNull(resultReceiver);
+
+ final MacAddress macAddress;
+ if (request.isSelfManaged()) {
+ macAddress = null;
+ } else {
+ macAddress = data.getParcelable(EXTRA_MAC_ADDRESS);
+ requireNonNull(macAddress);
+ }
+
+ processAssociationRequestApproval(request, callback, resultReceiver, macAddress);
+ }
+ };
+
+ private boolean mayAssociateWithoutPrompt(@NonNull AssociationRequest request,
+ @NonNull String packageName, @UserIdInt int userId) {
+ final String deviceProfile = request.getDeviceProfile();
if (deviceProfile != null) {
final boolean isRoleHolder = Binder.withCleanCallingIdentity(
() -> isRoleHolder(mContext, userId, packageName, deviceProfile));
@@ -252,8 +345,8 @@
String[] sameOemCerts = mContext.getResources()
.getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
- Signature[] signatures = mService.mPackageManagerInternal
- .getPackage(packageName).getSigningDetails().getSignatures();
+ Signature[] signatures = mPackageManager.getPackage(packageName).getSigningDetails()
+ .getSignatures();
String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures);
Set<String> sameOemPackageCerts =
@@ -274,47 +367,6 @@
return false;
}
- @NonNull
- private AndroidFuture<String> getDeviceProfilePermissionDescription(
- @Nullable String deviceProfile) {
- if (deviceProfile == null) {
- return AndroidFuture.completedFuture(null);
- }
-
- final AndroidFuture<String> result = new AndroidFuture<>();
- mService.mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
- deviceProfile, FgThread.getExecutor(), desc -> {
- try {
- result.complete(String.valueOf(desc));
- } catch (Exception e) {
- result.completeExceptionally(e);
- }
- });
- return result;
- }
-
-
- void dump(@NonNull PrintWriter pw) {
- pw.append("Discovery Service State:").append('\n');
- for (int i = 0, size = mServiceConnectors.size(); i < size; i++) {
- int userId = mServiceConnectors.keyAt(i);
- pw.append(" ")
- .append("u").append(Integer.toString(userId)).append(": ")
- .append(Objects.toString(mServiceConnectors.valueAt(i)))
- .append('\n');
- }
- }
-
- private final IBinder.DeathRecipient mBinderDeathRecipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- if (DEBUG) {
- Slog.d(TAG, "binderDied()");
- }
- mService.mMainHandler.post(AssociationRequestsProcessor.this::cleanup);
- }
- };
-
private static Set<String> getSameOemPackageCerts(
String packageName, String[] oemPackages, String[] sameOemCerts) {
Set<String> sameOemPackageCerts = new HashSet<>();
@@ -330,16 +382,19 @@
return sameOemPackageCerts;
}
- private static boolean withCatchingRemoteException(ThrowingRunnable runnable) {
- try {
- runnable.run();
- } catch (RemoteException e) {
- return false;
- }
- return true;
- }
+ /**
+ * Convert an instance of a "locally-defined" ResultReceiver to an instance of
+ * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
+ * unmarshall.
+ */
+ private static <T extends ResultReceiver> ResultReceiver prepareForIpc(T resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
- private interface ThrowingRunnable {
- void run() throws RemoteException;
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9c996f4..626128a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -406,7 +406,8 @@
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"create associations");
- mAssociationRequestsProcessor.process(request, packageName, userId, callback);
+ mAssociationRequestsProcessor.processNewAssociationRequest(
+ request, packageName, userId, callback);
}
@Override
@@ -717,8 +718,6 @@
.append(sDateFormat.format(time)).append('\n');
}
- mAssociationRequestsProcessor.dump(fout);
-
fout.append("Device Listener Services State:").append('\n');
for (int i = 0, size = mCompanionDevicePresenceController.mBoundServices.size();
i < size; i++) {
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index ea57089..3a8ee73 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -38,13 +38,11 @@
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
-import android.content.pm.PackageManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsService;
-import com.android.server.LocalServices;
import java.util.Map;
@@ -69,9 +67,7 @@
}
static void enforcePermissionsForAssociation(@NonNull Context context,
- @NonNull AssociationRequest request, @NonNull String packageName,
- @UserIdInt int userId) {
- final int packageUid = getPackageUid(userId, packageName);
+ @NonNull AssociationRequest request, int packageUid) {
enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid);
if (request.isSelfManaged()) {
@@ -207,11 +203,6 @@
}
}
- private static int getPackageUid(@UserIdInt int userId, @NonNull String packageName) {
- return LocalServices.getService(PackageManagerInternal.class)
- .getPackageUid(packageName, 0, userId);
- }
-
private static IAppOpsService getAppOpsService() {
if (sAppOpsService == null) {
synchronized (PermissionsUtils.class) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
new file mode 100644
index 0000000..241abaf
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+
+class AudioManagerShellCommand extends ShellCommand {
+ private static final String TAG = "AudioManagerShellCommand";
+
+ private final AudioService mService;
+
+ AudioManagerShellCommand(AudioService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch(cmd) {
+ case "set-surround-format-enabled":
+ return setSurroundFormatEnabled();
+ case "get-is-surround-format-enabled":
+ return getIsSurroundFormatEnabled();
+ case "set-encoded-surround-mode":
+ return setEncodedSurroundMode();
+ case "get-encoded-surround-mode":
+ return getEncodedSurroundMode();
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Audio manager commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" set-surround-format-enabled SURROUND_FORMAT IS_ENABLED");
+ pw.println(" Enables/disabled the SURROUND_FORMAT based on IS_ENABLED");
+ pw.println(" get-is-surround-format-enabled SURROUND_FORMAT");
+ pw.println(" Returns if the SURROUND_FORMAT is enabled");
+ pw.println(" set-encoded-surround-mode SURROUND_SOUND_MODE");
+ pw.println(" Sets the encoded surround sound mode to SURROUND_SOUND_MODE");
+ pw.println(" get-encoded-surround-mode");
+ pw.println(" Returns the encoded surround sound mode");
+ }
+
+ private int setSurroundFormatEnabled() {
+ String surroundFormatText = getNextArg();
+ String isSurroundFormatEnabledText = getNextArg();
+
+ if (surroundFormatText == null) {
+ getErrPrintWriter().println("Error: no surroundFormat specified");
+ return 1;
+ }
+
+ if (isSurroundFormatEnabledText == null) {
+ getErrPrintWriter().println("Error: no enabled value for surroundFormat specified");
+ return 1;
+ }
+
+ int surroundFormat = -1;
+ boolean isSurroundFormatEnabled = false;
+ try {
+ surroundFormat = Integer.parseInt(surroundFormatText);
+ isSurroundFormatEnabled = Boolean.parseBoolean(isSurroundFormatEnabledText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: wrong format specified for surroundFormat");
+ return 1;
+ }
+ if (surroundFormat < 0) {
+ getErrPrintWriter().println("Error: invalid value of surroundFormat");
+ return 1;
+ }
+
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ am.setSurroundFormatEnabled(surroundFormat, isSurroundFormatEnabled);
+ return 0;
+ }
+
+ private int getIsSurroundFormatEnabled() {
+ String surroundFormatText = getNextArg();
+
+ if (surroundFormatText == null) {
+ getErrPrintWriter().println("Error: no surroundFormat specified");
+ return 1;
+ }
+
+ int surroundFormat = -1;
+ try {
+ surroundFormat = Integer.parseInt(surroundFormatText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: wrong format specified for surroundFormat");
+ return 1;
+ }
+
+ if (surroundFormat < 0) {
+ getErrPrintWriter().println("Error: invalid value of surroundFormat");
+ return 1;
+ }
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ getOutPrintWriter().println("Value of enabled for " + surroundFormat + " is: "
+ + am.isSurroundFormatEnabled(surroundFormat));
+ return 0;
+ }
+
+ private int setEncodedSurroundMode() {
+ String encodedSurroundModeText = getNextArg();
+
+ if (encodedSurroundModeText == null) {
+ getErrPrintWriter().println("Error: no encodedSurroundMode specified");
+ return 1;
+ }
+
+ int encodedSurroundMode = -1;
+ try {
+ encodedSurroundMode = Integer.parseInt(encodedSurroundModeText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: wrong format specified for encoded surround mode");
+ return 1;
+ }
+
+ if (encodedSurroundMode < 0) {
+ getErrPrintWriter().println("Error: invalid value of encodedSurroundMode");
+ return 1;
+ }
+
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ am.setEncodedSurroundMode(encodedSurroundMode);
+ return 0;
+ }
+
+ private int getEncodedSurroundMode() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode());
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f857064..8615393 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -130,7 +130,9 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -1996,6 +1998,18 @@
}
}
+ @Override // Binder call
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_AUDIO_POLICY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing MANAGE_AUDIO_POLICY permission");
+ }
+ new AudioManagerShellCommand(AudioService.this).exec(this, in, out, err,
+ args, callback, resultReceiver);
+ }
+
/** @see AudioManager#getSurroundFormats() */
@Override
public Map<Integer, Boolean> getSurroundFormats() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3f55848..4c56999 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1072,6 +1072,13 @@
+ "setUserDisabledHdrTypesInternal");
return;
}
+
+ // Verify if userDisabledHdrTypes contains expected HDR types
+ if (!isSubsetOf(Display.HdrCapabilities.HDR_TYPES, userDisabledHdrTypes)) {
+ Slog.e(TAG, "userDisabledHdrTypes contains unexpected types");
+ return;
+ }
+
Arrays.sort(userDisabledHdrTypes);
if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
return;
@@ -1094,6 +1101,15 @@
}
}
+ private boolean isSubsetOf(int[] sortedSuperset, int[] subset) {
+ for (int i : subset) {
+ if (Arrays.binarySearch(sortedSuperset, i) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void setAreUserDisabledHdrTypesAllowedInternal(
boolean areUserDisabledHdrTypesAllowed) {
synchronized (mSyncRoot) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9412c93..43a850c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -20,9 +20,11 @@
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.ShellCommand;
+import android.util.Slog;
import android.view.Display;
import java.io.PrintWriter;
+import java.util.Arrays;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
@@ -60,6 +62,20 @@
return setAmbientColorTemperatureOverride();
case "constrain-launcher-metrics":
return setConstrainLauncherMetrics();
+ case "set-user-preferred-display-mode":
+ return setUserPreferredDisplayMode();
+ case "clear-user-preferred-display-mode":
+ return clearUserPreferredDisplayMode();
+ case "get-user-preferred-display-mode":
+ return getUserPreferredDisplayMode();
+ case "set-match-content-frame-rate-pref":
+ return setMatchContentFrameRateUserPreference();
+ case "get-match-content-frame-rate-pref":
+ return getMatchContentFrameRateUserPreference();
+ case "set-user-disabled-hdr-types":
+ return setUserDisabledHdrTypes();
+ case "get-user-disabled-hdr-types":
+ return getUserDisabledHdrTypes();
default:
return handleDefaultCommands(cmd);
}
@@ -93,6 +109,21 @@
pw.println(" constrain-launcher-metrics [true|false]");
pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for ");
pw.println(" Launcher.");
+ pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE");
+ pw.println(" Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
+ + "REFRESH-RATE");
+ pw.println(" clear-user-preferred-display-mode");
+ pw.println(" Clears the user preferred display mode");
+ pw.println(" get-user-preferred-display-mode");
+ pw.println(" Returns the user preferred display mode or null id no mode is set by user");
+ pw.println(" set-match-content-frame-rate-pref PREFERENCE");
+ pw.println(" Sets the match content frame rate preference as PREFERENCE ");
+ pw.println(" get-match-content-frame-rate-pref");
+ pw.println(" Returns the match content frame rate preference");
+ pw.println(" set-user-disabled-hdr-types TYPES...");
+ pw.println(" Sets the user disabled HDR types as TYPES");
+ pw.println(" get-user-disabled-hdr-types");
+ pw.println(" Returns the user disabled HDR types");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -166,4 +197,152 @@
mService.setShouldConstrainMetricsForLauncher(constrain);
return 0;
}
+
+ private int setUserPreferredDisplayMode() {
+ final String widthText = getNextArg();
+ if (widthText == null) {
+ getErrPrintWriter().println("Error: no width specified");
+ return 1;
+ }
+
+ final String heightText = getNextArg();
+ if (heightText == null) {
+ getErrPrintWriter().println("Error: no height specified");
+ return 1;
+ }
+
+ final String refreshRateText = getNextArg();
+ if (refreshRateText == null) {
+ getErrPrintWriter().println("Error: no refresh-rate specified");
+ return 1;
+ }
+
+ final int width, height;
+ final float refreshRate;
+ try {
+ width = Integer.parseInt(widthText);
+ height = Integer.parseInt(heightText);
+ refreshRate = Float.parseFloat(refreshRateText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of width, height or refresh rate");
+ return 1;
+ }
+ if (width < 0 || height < 0 || refreshRate <= 0.0f) {
+ getErrPrintWriter().println("Error: invalid value of width, height or refresh rate");
+ return 1;
+ }
+
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ dm.setUserPreferredDisplayMode(new Display.Mode(width, height, refreshRate));
+ return 0;
+ }
+
+ private int clearUserPreferredDisplayMode() {
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ dm.clearUserPreferredDisplayMode();
+ return 0;
+ }
+
+ private int getUserPreferredDisplayMode() {
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ final Display.Mode mode = dm.getUserPreferredDisplayMode();
+ if (mode == null) {
+ getOutPrintWriter().println("User preferred display mode: null");
+ return 0;
+ }
+
+ getOutPrintWriter().println("User preferred display mode: " + mode.getPhysicalWidth() + " "
+ + mode.getPhysicalHeight() + " " + mode.getRefreshRate());
+ return 0;
+ }
+
+ private int setMatchContentFrameRateUserPreference() {
+ final String matchContentFrameRatePrefText = getNextArg();
+ if (matchContentFrameRatePrefText == null) {
+ getErrPrintWriter().println("Error: no matchContentFrameRatePref specified");
+ return 1;
+ }
+
+ final int matchContentFrameRatePreference;
+ try {
+ matchContentFrameRatePreference = Integer.parseInt(matchContentFrameRatePrefText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of matchContentFrameRatePreference");
+ return 1;
+ }
+ if (matchContentFrameRatePreference < 0) {
+ getErrPrintWriter().println("Error: invalid value of matchContentFrameRatePreference");
+ return 1;
+ }
+
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+
+ final int refreshRateSwitchingType =
+ toRefreshRateSwitchingType(matchContentFrameRatePreference);
+ dm.setRefreshRateSwitchingType(refreshRateSwitchingType);
+ return 0;
+ }
+
+ private int getMatchContentFrameRateUserPreference() {
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ getOutPrintWriter().println("Match content frame rate type: "
+ + dm.getMatchContentFrameRateUserPreference());
+ return 0;
+ }
+
+ private int setUserDisabledHdrTypes() {
+ final String[] userDisabledHdrTypesText = getAllArgs();
+ if (userDisabledHdrTypesText == null) {
+ getErrPrintWriter().println("Error: no userDisabledHdrTypes specified");
+ return 1;
+ }
+
+ int[] userDisabledHdrTypes = new int[userDisabledHdrTypesText.length];
+ try {
+ int index = 0;
+ for (String userDisabledHdrType : userDisabledHdrTypesText) {
+ userDisabledHdrTypes[index++] = Integer.parseInt(userDisabledHdrType);
+ }
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes");
+ return 1;
+ }
+
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ dm.setUserDisabledHdrTypes(userDisabledHdrTypes);
+ return 0;
+ }
+
+ private int getUserDisabledHdrTypes() {
+ final Context context = mService.getContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ final int[] userDisabledHdrTypes = dm.getUserDisabledHdrTypes();
+ getOutPrintWriter().println("User disabled HDR types: "
+ + Arrays.toString(userDisabledHdrTypes));
+ return 0;
+ }
+
+ @DisplayManager.SwitchingType
+ private int toRefreshRateSwitchingType(
+ @DisplayManager.MatchContentFrameRateType int matchContentFrameRateType) {
+ switch (matchContentFrameRateType) {
+ case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER:
+ return DisplayManager.SWITCHING_TYPE_NONE;
+ case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY:
+ return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
+ case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS:
+ return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
+ case DisplayManager.MATCH_CONTENT_FRAMERATE_UNKNOWN:
+ default:
+ Slog.e(TAG, matchContentFrameRateType + " is not a valid value of "
+ + "matchContentFrameRate type.");
+ return -1;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2550e3a..ffef803 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -112,7 +112,6 @@
import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.EmergencyHelper;
import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.LocationAttributionHelper;
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPowerSaveModeHelper;
import com.android.server.location.injector.LocationUsageLogger;
@@ -1705,7 +1704,6 @@
private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
private final SystemDeviceIdleHelper mDeviceIdleHelper;
- private final LocationAttributionHelper mLocationAttributionHelper;
private final LocationUsageLogger mLocationUsageLogger;
// lazily instantiated since they may not always be used
@@ -1731,7 +1729,6 @@
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
- mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mLocationUsageLogger = new LocationUsageLogger();
}
@@ -1808,11 +1805,6 @@
}
@Override
- public LocationAttributionHelper getLocationAttributionHelper() {
- return mLocationAttributionHelper;
- }
-
- @Override
public synchronized EmergencyHelper getEmergencyHelper() {
if (mEmergencyCallHelper == null) {
mEmergencyCallHelper = new SystemEmergencyHelper(mContext);
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 1781588..699f143 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -16,6 +16,8 @@
package com.android.server.location.gnss;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+
import static com.android.server.location.gnss.GnssManagerService.D;
import static com.android.server.location.gnss.GnssManagerService.TAG;
@@ -32,7 +34,6 @@
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.injector.AppOpsHelper;
import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.LocationAttributionHelper;
import com.android.server.location.injector.LocationUsageLogger;
import com.android.server.location.injector.SettingsHelper;
@@ -68,25 +69,23 @@
@Nullable
@Override
protected void onActive() {
- mLocationAttributionHelper.reportHighPowerLocationStart(getIdentity());
+ mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
}
@Nullable
@Override
protected void onInactive() {
- mLocationAttributionHelper.reportHighPowerLocationStop(getIdentity());
+ mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
}
}
private final AppOpsHelper mAppOpsHelper;
- private final LocationAttributionHelper mLocationAttributionHelper;
private final LocationUsageLogger mLogger;
private final GnssNative mGnssNative;
public GnssMeasurementsProvider(Injector injector, GnssNative gnssNative) {
super(injector);
mAppOpsHelper = injector.getAppOpsHelper();
- mLocationAttributionHelper = injector.getLocationAttributionHelper();
mLogger = injector.getLocationUsageLogger();
mGnssNative = gnssNative;
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 173fd13..c0ce3a6 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -58,9 +58,6 @@
/** Returns a DeviceIdleHelper. */
DeviceIdleHelper getDeviceIdleHelper();
- /** Returns a LocationAttributionHelper. */
- LocationAttributionHelper getLocationAttributionHelper();
-
/** Returns an EmergencyHelper. */
EmergencyHelper getEmergencyHelper();
diff --git a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
deleted file mode 100644
index 4838752..0000000
--- a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.injector;
-
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-
-import static com.android.server.location.LocationManagerService.D;
-import static com.android.server.location.LocationManagerService.TAG;
-
-import android.location.util.identity.CallerIdentity;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Map;
-
-/**
- * Helps manage appop monitoring for multiple location clients.
- */
-public class LocationAttributionHelper {
-
- private final AppOpsHelper mAppOpsHelper;
-
- @GuardedBy("this")
- private final Map<CallerIdentity, Integer> mAttributions;
- @GuardedBy("this")
- private final Map<CallerIdentity, Integer> mHighPowerAttributions;
-
- public LocationAttributionHelper(AppOpsHelper appOpsHelper) {
- mAppOpsHelper = appOpsHelper;
-
- mAttributions = new ArrayMap<>();
- mHighPowerAttributions = new ArrayMap<>();
- }
-
- /**
- * Report normal location usage for the given caller in the given bucket, with a unique key.
- */
- public synchronized void reportLocationStart(CallerIdentity identity) {
- identity = CallerIdentity.forAggregation(identity);
-
- int count = mAttributions.getOrDefault(identity, 0);
- if (count == 0) {
- if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) {
- mAttributions.put(identity, 1);
- }
- } else {
- mAttributions.put(identity, count + 1);
- }
- }
-
- /**
- * Report normal location usage has stopped for the given caller in the given bucket, with a
- * unique key.
- */
- public synchronized void reportLocationStop(CallerIdentity identity) {
- identity = CallerIdentity.forAggregation(identity);
-
- int count = mAttributions.getOrDefault(identity, 0);
- if (count == 1) {
- mAttributions.remove(identity);
- mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity);
- } else if (count > 1) {
- mAttributions.put(identity, count - 1);
- }
- }
-
- /**
- * Report high power location usage for the given caller in the given bucket, with a unique
- * key.
- */
- public synchronized void reportHighPowerLocationStart(CallerIdentity identity) {
- identity = CallerIdentity.forAggregation(identity);
-
- int count = mHighPowerAttributions.getOrDefault(identity, 0);
- if (count == 0) {
- if (D) {
- Log.v(TAG, "starting high power location attribution for " + identity);
- }
- if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) {
- mHighPowerAttributions.put(identity, 1);
- }
- } else {
- mHighPowerAttributions.put(identity, count + 1);
- }
- }
-
- /**
- * Report high power location usage has stopped for the given caller in the given bucket,
- * with a unique key.
- */
- public synchronized void reportHighPowerLocationStop(CallerIdentity identity) {
- identity = CallerIdentity.forAggregation(identity);
-
- int count = mHighPowerAttributions.getOrDefault(identity, 0);
- if (count == 1) {
- if (D) {
- Log.v(TAG, "stopping high power location attribution for " + identity);
- }
- mHighPowerAttributions.remove(identity);
- mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity);
- } else if (count > 1) {
- mHighPowerAttributions.put(identity, count - 1);
- }
- }
-}
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 ede8b32..c1d8e78 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -16,6 +16,8 @@
package com.android.server.location.provider;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.app.compat.CompatChanges.isChangeEnabled;
import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
import static android.location.LocationManager.GPS_PROVIDER;
@@ -32,6 +34,7 @@
import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -98,7 +101,6 @@
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
import com.android.server.location.injector.AppOpsHelper;
import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.LocationAttributionHelper;
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
import com.android.server.location.injector.LocationPowerSaveModeHelper;
@@ -191,7 +193,7 @@
private final ILocationListener mListener;
- protected LocationListenerTransport(ILocationListener listener) {
+ LocationListenerTransport(ILocationListener listener) {
mListener = Objects.requireNonNull(listener);
}
@@ -300,7 +302,7 @@
private final ILocationCallback mCallback;
- protected GetCurrentLocationTransport(ILocationCallback callback) {
+ GetCurrentLocationTransport(ILocationCallback callback) {
mCallback = Objects.requireNonNull(callback);
}
@@ -411,7 +413,7 @@
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
- mLocationAttributionHelper.reportLocationStart(getIdentity());
+ mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, getIdentity());
}
onHighPowerUsageChanged();
@@ -426,7 +428,7 @@
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
- mLocationAttributionHelper.reportLocationStop(getIdentity());
+ mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, getIdentity());
}
onProviderListenerInactive();
@@ -500,11 +502,9 @@
if (!getRequest().isHiddenFromAppOps()) {
if (mIsUsingHighPower) {
- mLocationAttributionHelper.reportHighPowerLocationStart(
- getIdentity());
+ mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
} else {
- mLocationAttributionHelper.reportHighPowerLocationStop(
- getIdentity());
+ mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
}
}
}
@@ -1015,7 +1015,7 @@
protected final class LocationListenerRegistration extends LocationRegistration implements
IBinder.DeathRecipient {
- protected LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
+ LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
super(request, identity, transport, permissionLevel);
}
@@ -1080,7 +1080,7 @@
protected final class LocationPendingIntentRegistration extends LocationRegistration implements
PendingIntent.CancelListener {
- protected LocationPendingIntentRegistration(LocationRequest request,
+ LocationPendingIntentRegistration(LocationRequest request,
CallerIdentity identity, LocationPendingIntentTransport transport,
@PermissionLevel int permissionLevel) {
super(request, identity, transport, permissionLevel);
@@ -1089,13 +1089,15 @@
@GuardedBy("mLock")
@Override
protected void onLocationListenerRegister() {
- ((PendingIntent) getKey()).registerCancelListener(this);
+ if (!((PendingIntent) getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
+ remove();
+ }
}
@GuardedBy("mLock")
@Override
protected void onLocationListenerUnregister() {
- ((PendingIntent) getKey()).unregisterCancelListener(this);
+ ((PendingIntent) getKey()).removeCancelListener(this);
}
@Override
@@ -1138,7 +1140,7 @@
private long mExpirationRealtimeMs = Long.MAX_VALUE;
- protected GetCurrentLocationListenerRegistration(LocationRequest request,
+ GetCurrentLocationListenerRegistration(LocationRequest request,
CallerIdentity identity, LocationTransport transport, int permissionLevel) {
super(request, identity, transport, permissionLevel);
}
@@ -1347,7 +1349,6 @@
protected final AppForegroundHelper mAppForegroundHelper;
protected final LocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
protected final ScreenInteractiveHelper mScreenInteractiveHelper;
- protected final LocationAttributionHelper mLocationAttributionHelper;
protected final LocationUsageLogger mLocationUsageLogger;
protected final LocationFudger mLocationFudger;
@@ -1415,7 +1416,6 @@
mAppForegroundHelper = injector.getAppForegroundHelper();
mLocationPowerSaveModeHelper = injector.getLocationPowerSaveModeHelper();
mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
- mLocationAttributionHelper = injector.getLocationAttributionHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fdcf1fc..0564e85 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -18,7 +18,6 @@
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
-import android.Manifest;
import android.accounts.IAccountManager;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -171,20 +170,6 @@
@Override
public int onCommand(String cmd) {
- switch (Binder.getCallingUid()) {
- case Process.ROOT_UID:
- case Process.SHELL_UID:
- break;
- default:
- // This is called from a test and is allowed as non-shell with the right permission
- if ("install-incremental".equals(cmd)) {
- mContext.enforceCallingPermission(Manifest.permission.USE_SYSTEM_DATA_LOADERS,
- "Caller missing USE_SYSTEM_DATA_LOADERS permission to use " + cmd);
- } else {
- throw new IllegalArgumentException("Caller must be root or shell");
- }
- }
-
if (cmd == null) {
return handleDefaultCommands(cmd);
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index a3b0e3e..111087f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1197,7 +1197,6 @@
@Nullable @UserIdInt Integer userId,
@NonNull Function<String, PackageStateInternal> pkgSettingFunction)
throws NameNotFoundException {
- mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
synchronized (mLock) {
mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index be01646..173545c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4441,6 +4441,10 @@
}
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
+ if (userId != getCurrentUserId()) {
+ return;
+ }
+
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
FONT_SCALE, 1.0f, userId);
@@ -4457,6 +4461,10 @@
}
private void updateFontWeightAdjustmentIfNeeded(@UserIdInt int userId) {
+ if (userId != getCurrentUserId()) {
+ return;
+ }
+
final int fontWeightAdjustment =
Settings.Secure.getIntForUser(
mContext.getContentResolver(),
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 06f5aed..f0f779d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -997,7 +997,7 @@
}
}
- if (gnssHalAidl != nullptr) {
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
sp<IAGnssAidl> agnssAidl;
auto status = gnssHalAidl->getExtensionAGnss(&agnssAidl);
if (checkAidlStatus(status, "Unable to get a handle to AGnss interface.")) {
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index fd295c0..9e83f8e 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -278,7 +278,7 @@
assertThat(mBackupManagerService.getPendingInits()).isEmpty();
assertThat(mBackupManagerService.isBackupRunning()).isFalse();
- assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+ assertThat(mBackupManagerService.getOperationStorage().numOperations()).isEqualTo(0);
verify(mOldJournal).delete();
}
@@ -449,7 +449,7 @@
assertThat(mBackupManagerService.getPendingInits()).isEmpty();
assertThat(mBackupManagerService.isBackupRunning()).isFalse();
- assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+ assertThat(mBackupManagerService.getOperationStorage().numOperations()).isEqualTo(0);
assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(1234L);
verify(mBackupManagerService).writeRestoreTokens();
verify(mOldJournal).delete();
@@ -2665,6 +2665,7 @@
KeyValueBackupTask task =
new KeyValueBackupTask(
mBackupManagerService,
+ mBackupManagerService.getOperationStorage(),
transportMock.mTransportConnection,
transportMock.transportData.transportDirName,
queue,
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index 9eb99ae..e0812d6 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -51,6 +51,7 @@
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
@@ -98,6 +99,7 @@
@Mock private IRestoreObserver mObserver;
@Mock private IBackupManagerMonitor mMonitor;
@Mock private BackupEligibilityRules mBackupEligibilityRules;
+ @Mock private OperationStorage mOperationStorage;
private ShadowLooper mShadowBackupLooper;
private ShadowApplication mShadowApplication;
private UserBackupManagerService.BackupWakeLock mWakeLock;
@@ -132,7 +134,9 @@
// We need to mock BMS timeout parameters before initializing the BackupHandler since
// the constructor of BackupHandler relies on it.
when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters);
- BackupHandler backupHandler = new BackupHandler(mBackupManagerService, handlerThread);
+
+ BackupHandler backupHandler =
+ new BackupHandler(mBackupManagerService, mOperationStorage, handlerThread);
mWakeLock = createBackupWakeLock(application);
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
index 77b5b61..fc3ec7b 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
@@ -120,7 +120,6 @@
when(backupManagerService.getTransportManager()).thenReturn(transportManager);
when(backupManagerService.getPackageManager()).thenReturn(packageManager);
when(backupManagerService.getBackupHandler()).thenReturn(backupHandler);
- when(backupManagerService.getCurrentOpLock()).thenReturn(new Object());
when(backupManagerService.getQueueLock()).thenReturn(new Object());
when(backupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
when(backupManagerService.getWakelock()).thenReturn(wakeLock);
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
index 06b7fb7..6a7d031 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import com.android.server.backup.DataChangedJournal;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.keyvalue.KeyValueBackupReporter;
@@ -56,6 +57,7 @@
@Implementation
protected void __constructor__(
UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
TransportConnection transportConnection,
String transportDirName,
List<String> queue,
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
index 71010a9..d985e1b 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -21,6 +21,7 @@
import android.app.backup.IRestoreObserver;
import android.content.pm.PackageInfo;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
@@ -57,6 +58,7 @@
@Implementation
protected void __constructor__(
UserBackupManagerService backupManagerService,
+ OperationStorage operationStorage,
TransportConnection transportConnection,
IRestoreObserver observer,
IBackupManagerMonitor monitor,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index e633850..005d3e8 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -18,6 +18,7 @@
import android.content.pm.parsing.component.ParsedProcess
import android.content.pm.parsing.component.ParsedProcessImpl
+import android.util.ArrayMap
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
@@ -29,6 +30,8 @@
override val excludedMethods = listOf(
// Copying method
"addStateFrom",
+ // Utility method
+ "putAppClassNameForPackage",
)
override val baseParams = listOf(
@@ -39,6 +42,9 @@
)
override fun extraParams() = listOf(
- getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission"))
+ getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")),
+ getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply {
+ put("package1", "classname1");
+ }),
)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d9dbf48..d1d7cc6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.content.pm.SigningDetails
import android.content.pm.parsing.component.ParsedActivityImpl
import android.content.pm.parsing.component.ParsedIntentInfoImpl
import android.content.pm.verify.domain.DomainVerificationManager
@@ -27,7 +26,6 @@
import android.os.Build
import android.os.Process
import android.util.ArraySet
-import android.util.IndentingPrintWriter
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.parsing.pkg.AndroidPackage
@@ -48,7 +46,6 @@
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verifyNoMoreInteractions
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
@@ -207,14 +204,6 @@
service(Type.QUERENT, "getInfo") {
getDomainVerificationInfo(it.targetPackageName)
},
- service(Type.QUERENT, "printState") {
- printState(mock(IndentingPrintWriter::class.java), null, null)
- },
- service(Type.QUERENT, "printStateInternal") {
- printState(mock(IndentingPrintWriter::class.java), null, null) {
- mockPkgState(it, UUID.randomUUID())
- }
- },
service(Type.VERIFIER, "setStatus") {
setDomainVerificationStatus(
it.targetDomainSetId,
@@ -322,7 +311,6 @@
}
)
}
- whenever(signingDetails) { SigningDetails.UNKNOWN }
}
fun mockPkgState(packageName: String, domainSetId: UUID) =
@@ -339,7 +327,6 @@
}
}
whenever(isSystem) { false }
- whenever(signingDetails) { SigningDetails.UNKNOWN }
}
}
@@ -807,12 +794,8 @@
}
val valueAsInt = value as? Int
- if (valueAsInt != null) {
- if (valueAsInt == DomainVerificationManager.STATUS_OK) {
- throw AssertionError("Expected call to return false, was $value")
- }
- } else {
- throw AssertionError("Expected call to fail")
+ if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
+ throw AssertionError("Expected call to return false, was $value")
}
} catch (e: SecurityException) {
} catch (e: PackageManager.NameNotFoundException) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java
index d728451..189396f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java
@@ -31,7 +31,7 @@
private static class AppOp {
AppOp() {}
boolean mAllowed = true;
- boolean mStarted = false;
+ int mStarted = 0;
int mNoteCount = 0;
}
@@ -49,7 +49,7 @@
public boolean isAppOpStarted(int appOp, String packageName) {
AppOp myAppOp = getOp(packageName, appOp);
- return myAppOp.mStarted;
+ return myAppOp.mStarted > 0;
}
public int getAppOpNoteCount(int appOp, String packageName) {
@@ -63,16 +63,15 @@
if (!myAppOp.mAllowed) {
return false;
}
- Preconditions.checkState(!myAppOp.mStarted);
- myAppOp.mStarted = true;
+ myAppOp.mStarted++;
return true;
}
@Override
public void finishOp(int appOp, CallerIdentity callerIdentity) {
AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp);
- Preconditions.checkState(myAppOp.mStarted);
- myAppOp.mStarted = false;
+ Preconditions.checkState(myAppOp.mStarted > 0);
+ myAppOp.mStarted--;
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
deleted file mode 100644
index 94dcdf9..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.injector;
-
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.location.util.identity.CallerIdentity;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LocationAttributionHelperTest {
-
- @Mock private AppOpsHelper mAppOpsHelper;
-
- private LocationAttributionHelper mHelper;
-
- @Before
- public void setUp() {
- initMocks(this);
-
- when(mAppOpsHelper.startOpNoThrow(anyInt(), any(CallerIdentity.class))).thenReturn(true);
-
- mHelper = new LocationAttributionHelper(mAppOpsHelper);
- }
-
- @Test
- public void testLocationMonitoring() {
- CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null);
- CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null);
-
- mHelper.reportLocationStart(caller1);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller1));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller1));
-
- mHelper.reportLocationStart(caller1);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller1));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller1));
-
- mHelper.reportLocationStart(caller2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller2));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller2));
-
- mHelper.reportLocationStart(caller2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller2));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller2));
-
- mHelper.reportLocationStop(caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller1));
- mHelper.reportLocationStop(caller1);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller1));
-
- mHelper.reportLocationStop(caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
- CallerIdentity.forAggregation(caller2));
- mHelper.reportLocationStop(caller2);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller2));
- }
-
- @Test
- public void testHighPowerLocationMonitoring() {
- CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null);
- CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null);
-
- mHelper.reportHighPowerLocationStart(caller1);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller1));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller1));
-
- mHelper.reportHighPowerLocationStart(caller1);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller1));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller1));
-
- mHelper.reportHighPowerLocationStart(caller2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller2));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller2));
-
- mHelper.reportHighPowerLocationStart(caller2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller2));
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller2));
-
- mHelper.reportHighPowerLocationStop(caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller1));
- mHelper.reportHighPowerLocationStop(caller1);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller1));
-
- mHelper.reportHighPowerLocationStop(caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller2));
- mHelper.reportHighPowerLocationStop(caller2);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
- CallerIdentity.forAggregation(caller2));
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index bd24cfd..02cacb7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -33,7 +33,6 @@
private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
private final FakeDeviceStationaryHelper mDeviceStationaryHelper;
private final FakeDeviceIdleHelper mDeviceIdleHelper;
- private final LocationAttributionHelper mLocationAttributionHelper;
private final FakeEmergencyHelper mEmergencyHelper;
private final LocationUsageLogger mLocationUsageLogger;
@@ -49,7 +48,6 @@
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
mDeviceIdleHelper = new FakeDeviceIdleHelper();
- mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mEmergencyHelper = new FakeEmergencyHelper();
mLocationUsageLogger = new LocationUsageLogger();
}
@@ -110,11 +108,6 @@
}
@Override
- public LocationAttributionHelper getLocationAttributionHelper() {
- return mLocationAttributionHelper;
- }
-
- @Override
public EmergencyHelper getEmergencyHelper() {
return mEmergencyHelper;
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index c36e1a8..bc95341 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -35,6 +35,7 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.internal.LifecycleOperationStorage;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.params.BackupParams;
import com.android.server.backup.transport.BackupTransportClient;
@@ -60,7 +61,7 @@
@Mock TransportConnection mTransportConnection;
@Mock BackupTransportClient mBackupTransport;
@Mock BackupEligibilityRules mBackupEligibilityRules;
-
+ @Mock LifecycleOperationStorage mOperationStorage;
private TestBackupService mService;
@@ -68,7 +69,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mService = new TestBackupService(mContext, mPackageManager);
+ mService = new TestBackupService(mContext, mPackageManager, mOperationStorage);
mService.setEnabled(true);
mService.setSetupComplete(true);
}
@@ -173,8 +174,9 @@
boolean isEnabledStatePersisted = false;
boolean shouldUseNewBackupEligibilityRules = false;
- TestBackupService(Context context, PackageManager packageManager) {
- super(context, packageManager);
+ TestBackupService(Context context, PackageManager packageManager,
+ LifecycleOperationStorage operationStorage) {
+ super(context, packageManager, operationStorage);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
index fa35e3f..3c79d8b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
@@ -18,7 +18,6 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import android.os.HandlerThread;
@@ -28,6 +27,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.UserBackupManagerService;
import org.junit.After;
@@ -46,6 +46,7 @@
private static final int MESSAGE_TIMEOUT_MINUTES = 1;
@Mock private UserBackupManagerService mUserBackupManagerService;
+ @Mock private OperationStorage mOperationStorage;
@Mock private BackupAgentTimeoutParameters mTimeoutParameters;
private HandlerThread mHandlerThread;
@@ -114,7 +115,7 @@
private final boolean mShouldStop;
TestBackupHandler(boolean shouldStop) {
- super(mUserBackupManagerService, mHandlerThread);
+ super(mUserBackupManagerService, mOperationStorage, mHandlerThread);
mShouldStop = shouldStop;
}
diff --git a/tests/MultiUser/Android.bp b/tests/MultiUser/Android.bp
new file mode 100644
index 0000000..bde309f
--- /dev/null
+++ b/tests/MultiUser/Android.bp
@@ -0,0 +1,27 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MultiUserTests",
+ platform_apis: true,
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "services.core",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ ],
+ certificate: "platform",
+ test_suites: ["device-tests"],
+}
diff --git a/tests/MultiUser/AndroidManifest.xml b/tests/MultiUser/AndroidManifest.xml
new file mode 100644
index 0000000..9a25c09
--- /dev/null
+++ b/tests/MultiUser/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.multiuser">
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.test.multiuser">
+ </instrumentation>
+</manifest>
diff --git a/tests/MultiUser/TEST_MAPPING b/tests/MultiUser/TEST_MAPPING
new file mode 100644
index 0000000..0dbef6c
--- /dev/null
+++ b/tests/MultiUser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "MultiUserTests"
+ }
+ ]
+}
diff --git a/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java b/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java
new file mode 100644
index 0000000..1521cc6
--- /dev/null
+++ b/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.multiuser;
+
+import static android.provider.Settings.Secure.FONT_WEIGHT_ADJUSTMENT;
+import static android.provider.Settings.System.FONT_SCALE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiUserSettingsTests {
+ private final Context mContext = getInstrumentation().getTargetContext();
+ private final ContentResolver mContentResolver = mContext.getContentResolver();
+
+ private static void waitForBroadcastIdle() throws InterruptedException {
+ final int sleepDuration = 1000;
+ final String cmdAmWaitForBroadcastIdle = "am wait-for-broadcast-idle";
+
+ Thread.sleep(sleepDuration);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand(cmdAmWaitForBroadcastIdle);
+ Thread.sleep(sleepDuration);
+ }
+
+ private float getGlobalFontScale() {
+ return mContext.getResources().getConfiguration().fontScale;
+ }
+
+ private int getGlobalFontWeight() {
+ return mContext.getResources().getConfiguration().fontWeightAdjustment;
+ }
+
+ private float getFontScaleOfUser(int userId) {
+ return Settings.System.getFloatForUser(mContentResolver, FONT_SCALE, 1, userId);
+ }
+
+ private int getFontWeightOfUser(int userId) {
+ return Settings.Secure.getIntForUser(mContentResolver, FONT_WEIGHT_ADJUSTMENT, 1, userId);
+ }
+
+ private void setFontScaleOfUser(float fontScale, int userId) throws InterruptedException {
+ Settings.System.putFloatForUser(mContentResolver, FONT_SCALE, fontScale, userId);
+ waitForBroadcastIdle();
+ }
+
+ private void setFontWeightOfUser(int fontWeight, int userId) throws InterruptedException {
+ Settings.Secure.putIntForUser(mContentResolver, FONT_WEIGHT_ADJUSTMENT, fontWeight, userId);
+ waitForBroadcastIdle();
+ }
+
+ @Test
+ public void testChangingFontScaleOfABackgroundUser_shouldNotAffectUI()
+ throws InterruptedException {
+
+ Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+ UserManager userManager = UserManager.get(mContext);
+
+ final int backgroundUserId = userManager.createUser("test_user",
+ UserManager.USER_TYPE_FULL_SECONDARY, 0).id;
+ final float oldFontScaleOfBgUser = getFontScaleOfUser(backgroundUserId);
+ final float oldGlobalFontScale = getGlobalFontScale();
+ final float newFontScaleOfBgUser = 1 + Math.max(oldGlobalFontScale, oldFontScaleOfBgUser);
+
+ try {
+ setFontScaleOfUser(newFontScaleOfBgUser, backgroundUserId);
+ final float newGlobalFontScale = getGlobalFontScale();
+ assertEquals(oldGlobalFontScale, newGlobalFontScale, 0);
+ } finally {
+ setFontScaleOfUser(oldFontScaleOfBgUser, backgroundUserId);
+ userManager.removeUser(backgroundUserId);
+ }
+ }
+
+ @Test
+ public void testChangingFontWeightOfABackgroundUser_shouldNotAffectUI()
+ throws InterruptedException {
+
+ Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+ UserManager userManager = UserManager.get(mContext);
+
+ final int backgroundUserId = userManager.createUser("test_user",
+ UserManager.USER_TYPE_FULL_SECONDARY, 0).id;
+ final int oldFontWeightOfBgUser = getFontWeightOfUser(backgroundUserId);
+ final int oldGlobalFontWeight = getGlobalFontWeight();
+ final int newFontWeightOfBgUser = 2 * Math.max(oldGlobalFontWeight, oldFontWeightOfBgUser);
+
+ try {
+ setFontWeightOfUser(newFontWeightOfBgUser, backgroundUserId);
+ final int newGlobalFontWeight = getGlobalFontWeight();
+ assertEquals(oldGlobalFontWeight, newGlobalFontWeight);
+ } finally {
+ setFontWeightOfUser(oldFontWeightOfBgUser, backgroundUserId);
+ userManager.removeUser(backgroundUserId);
+ }
+ }
+
+ @Test
+ public void testChangingFontScaleOfTheForegroundUser_shouldAffectUI()
+ throws InterruptedException {
+
+ Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+ final int currentUserId = mContext.getUserId();
+ final float oldFontScale = getFontScaleOfUser(currentUserId);
+ final float newFontScale = 1 + oldFontScale;
+
+ try {
+ setFontScaleOfUser(newFontScale, currentUserId);
+ final float globalFontScale = getGlobalFontScale();
+ assertEquals(newFontScale, globalFontScale, 0);
+ } finally {
+ setFontScaleOfUser(oldFontScale, currentUserId);
+ }
+ }
+
+ @Test
+ public void testChangingFontWeightOfTheForegroundUser_shouldAffectUI()
+ throws InterruptedException {
+
+ Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+ final int currentUserId = mContext.getUserId();
+ final int oldFontWeight = getFontWeightOfUser(currentUserId);
+ final int newFontWeight = 2 * oldFontWeight;
+
+ try {
+ setFontWeightOfUser(newFontWeight, currentUserId);
+ final int globalFontWeight = getGlobalFontWeight();
+ assertEquals(newFontWeight, globalFontWeight);
+ } finally {
+ setFontWeightOfUser(oldFontWeight, currentUserId);
+ }
+ }
+}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index d204190..d432341 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -588,10 +588,21 @@
child_el->name == "provider" || child_el->name == "receiver" ||
child_el->name == "service") {
FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", child_el);
+ continue;
}
if (child_el->name == "activity-alias") {
FullyQualifyClassName(original_package, xml::kSchemaAndroid, "targetActivity", child_el);
+ continue;
+ }
+
+ if (child_el->name == "processes") {
+ for (xml::Element* grand_child_el : child_el->GetChildElements()) {
+ if (grand_child_el->name == "process") {
+ FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", grand_child_el);
+ }
+ }
+ continue;
}
}
}