Merge "Add owners to wear specific resources."
diff --git a/Android.bp b/Android.bp
index 778aa55..95cdea0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,7 @@
// Java/AIDL sources under frameworks/base
":framework-annotations",
":framework-blobstore-sources",
+ ":framework-connectivity-nsd-sources",
":framework-core-sources",
":framework-drm-sources",
":framework-graphics-nonupdatable-sources",
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index d729019..e1e6e47 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -357,7 +357,7 @@
pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
- pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+ pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
pw.println();
pw.println("Actions:");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 7bf0e6d..a4e7b80 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -33,8 +33,8 @@
/** Lazily populated set of rewards covered by this policy. */
private final SparseArray<Reward> mRewards = new SparseArray<>();
private final int[] mCostModifiers;
- private final long mMaxSatiatedBalance;
- private final long mMaxSatiatedCirculation;
+ private long mMaxSatiatedBalance;
+ private long mMaxSatiatedCirculation;
CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
super(irs);
@@ -53,6 +53,19 @@
mCostModifiers[i] = costModifiers.valueAt(i);
}
+ updateMaxBalances();
+ }
+
+ @Override
+ void setup() {
+ super.setup();
+ for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
+ mEnabledEconomicPolicies.valueAt(i).setup();
+ }
+ updateMaxBalances();
+ }
+
+ private void updateMaxBalances() {
long max = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance();
@@ -67,14 +80,6 @@
}
@Override
- void setup() {
- super.setup();
- for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
- mEnabledEconomicPolicies.valueAt(i).setup();
- }
- }
-
- @Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
long min = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 437a101..20a300a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -847,15 +847,16 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
+ boolean dumpAll = true;
if (!ArrayUtils.isEmpty(args)) {
String arg = args[0];
if ("-h".equals(arg) || "--help".equals(arg)) {
dumpHelp(pw);
return;
} else if ("-a".equals(arg)) {
- // -a is passed when dumping a bug report so we have to acknowledge the
- // argument. However, we currently don't do anything differently for bug
- // reports.
+ // -a is passed when dumping a bug report. Bug reports have a time limit for
+ // each service dump, so we can't dump everything.
+ dumpAll = false;
} else if (arg.length() > 0 && arg.charAt(0) == '-') {
pw.println("Unknown option: " + arg);
return;
@@ -864,7 +865,7 @@
final long identityToken = Binder.clearCallingIdentity();
try {
- dumpInternal(new IndentingPrintWriter(pw, " "));
+ dumpInternal(new IndentingPrintWriter(pw, " "), dumpAll);
} finally {
Binder.restoreCallingIdentity(identityToken);
}
@@ -1098,7 +1099,7 @@
pw.println(" [package] is an optional package name to limit the output to.");
}
- private void dumpInternal(final IndentingPrintWriter pw) {
+ private void dumpInternal(final IndentingPrintWriter pw, final boolean dumpAll) {
synchronized (mLock) {
pw.print("Is enabled: ");
pw.println(mIsEnabled);
@@ -1127,7 +1128,7 @@
mCompleteEconomicPolicy.dump(pw);
pw.println();
- mScribe.dumpLocked(pw);
+ mScribe.dumpLocked(pw, dumpAll);
pw.println();
mAgent.dumpLocked(pw);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 2318026..1f8ce26 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -332,7 +332,7 @@
pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
- pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+ pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
pw.println();
pw.println("Actions:");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 42f3d9a..86968ef 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -509,7 +509,7 @@
}
@GuardedBy("mIrs.getLock()")
- void dumpLocked(IndentingPrintWriter pw) {
+ void dumpLocked(IndentingPrintWriter pw, boolean dumpAll) {
pw.println("Ledgers:");
pw.increaseIndent();
mLedgers.forEach((userId, pkgName, ledger) -> {
@@ -519,7 +519,7 @@
}
pw.println();
pw.increaseIndent();
- ledger.dump(pw, MAX_NUM_TRANSACTION_DUMP);
+ ledger.dump(pw, dumpAll ? Integer.MAX_VALUE : MAX_NUM_TRANSACTION_DUMP);
pw.decreaseIndent();
});
pw.decreaseIndent();
diff --git a/core/api/current.txt b/core/api/current.txt
index 2501c02..fd429f0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3264,9 +3264,9 @@
method public float getScale();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
- field public static final int DEFAULT_MODE = 0; // 0x0
- field public static final int FULLSCREEN_MODE = 1; // 0x1
- field public static final int WINDOW_MODE = 2; // 0x2
+ field public static final int MAGNIFICATION_MODE_DEFAULT = 0; // 0x0
+ field public static final int MAGNIFICATION_MODE_FULLSCREEN = 1; // 0x1
+ field public static final int MAGNIFICATION_MODE_WINDOW = 2; // 0x2
}
public static final class MagnificationConfig.Builder {
@@ -3275,7 +3275,7 @@
method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
- method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(float);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(@FloatRange(from=1.0f, to=8.0f) float);
}
public final class TouchInteractionController {
@@ -9561,6 +9561,20 @@
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
}
+ public final class BluetoothLeAudioCodecConfig {
+ method @NonNull public String getCodecName();
+ method public int getCodecType();
+ method public static int getMaxCodecType();
+ field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+ field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
+ }
+
+ public static final class BluetoothLeAudioCodecConfig.Builder {
+ ctor public BluetoothLeAudioCodecConfig.Builder();
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
+ method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
+ }
+
public final class BluetoothManager {
method public android.bluetooth.BluetoothAdapter getAdapter();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
@@ -9967,6 +9981,9 @@
public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public String getDeviceProfile();
+ method @Nullable public CharSequence getDisplayName();
+ method public boolean isSingleDevice();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
@@ -16443,6 +16460,7 @@
ctor public SurfaceTexture(boolean);
method public void attachToGLContext(int);
method public void detachFromGLContext();
+ method public long getDataSpace();
method public long getTimestamp();
method public void getTransformMatrix(float[]);
method public boolean isReleased();
@@ -17660,6 +17678,52 @@
method public int getMinFrequency();
}
+ public final class DataSpace {
+ method public static long getRange(long);
+ method public static long getStandard(long);
+ method public static long getTransfer(long);
+ method public static long pack(long, long, long);
+ field public static final long DATASPACE_ADOBE_RGB = 151715840L; // 0x90b0000L
+ field public static final long DATASPACE_BT2020 = 147193856L; // 0x8c60000L
+ field public static final long DATASPACE_BT2020_PQ = 163971072L; // 0x9c60000L
+ field public static final long DATASPACE_BT601_525 = 281280512L; // 0x10c40000L
+ field public static final long DATASPACE_BT601_625 = 281149440L; // 0x10c20000L
+ field public static final long DATASPACE_BT709 = 281083904L; // 0x10c10000L
+ field public static final long DATASPACE_DCI_P3 = 155844608L; // 0x94a0000L
+ field public static final long DATASPACE_DISPLAY_P3 = 143261696L; // 0x88a0000L
+ field public static final long DATASPACE_JFIF = 146931712L; // 0x8c20000L
+ field public static final long DATASPACE_SCRGB = 411107328L; // 0x18810000L
+ field public static final long DATASPACE_SCRGB_LINEAR = 406913024L; // 0x18410000L
+ field public static final long DATASPACE_SRGB = 142671872L; // 0x8810000L
+ field public static final long DATASPACE_SRGB_LINEAR = 138477568L; // 0x8410000L
+ field public static final long DATASPACE_UNKNOWN = 0L; // 0x0L
+ field public static final long RANGE_EXTENDED = 402653184L; // 0x18000000L
+ field public static final long RANGE_FULL = 134217728L; // 0x8000000L
+ field public static final long RANGE_LIMITED = 268435456L; // 0x10000000L
+ field public static final long RANGE_UNSPECIFIED = 0L; // 0x0L
+ field public static final long STANDARD_ADOBE_RGB = 720896L; // 0xb0000L
+ field public static final long STANDARD_BT2020 = 393216L; // 0x60000L
+ field public static final long STANDARD_BT2020_CONSTANT_LUMINANCE = 458752L; // 0x70000L
+ field public static final long STANDARD_BT470M = 524288L; // 0x80000L
+ field public static final long STANDARD_BT601_525 = 262144L; // 0x40000L
+ field public static final long STANDARD_BT601_525_UNADJUSTED = 327680L; // 0x50000L
+ field public static final long STANDARD_BT601_625 = 131072L; // 0x20000L
+ field public static final long STANDARD_BT601_625_UNADJUSTED = 196608L; // 0x30000L
+ field public static final long STANDARD_BT709 = 65536L; // 0x10000L
+ field public static final long STANDARD_DCI_P3 = 655360L; // 0xa0000L
+ field public static final long STANDARD_FILM = 589824L; // 0x90000L
+ field public static final long STANDARD_UNSPECIFIED = 0L; // 0x0L
+ field public static final long TRANSFER_GAMMA2_2 = 16777216L; // 0x1000000L
+ field public static final long TRANSFER_GAMMA2_6 = 20971520L; // 0x1400000L
+ field public static final long TRANSFER_GAMMA2_8 = 25165824L; // 0x1800000L
+ field public static final long TRANSFER_HLG = 33554432L; // 0x2000000L
+ field public static final long TRANSFER_LINEAR = 4194304L; // 0x400000L
+ field public static final long TRANSFER_SMPTE_170M = 12582912L; // 0xc00000L
+ field public static final long TRANSFER_SRGB = 8388608L; // 0x800000L
+ field public static final long TRANSFER_ST2084 = 29360128L; // 0x1c00000L
+ field public static final long TRANSFER_UNSPECIFIED = 0L; // 0x0L
+ }
+
public class GeomagneticField {
ctor public GeomagneticField(float, float, float, long);
method public float getDeclination();
@@ -21598,6 +21662,7 @@
public abstract class Image implements java.lang.AutoCloseable {
method public abstract void close();
method public android.graphics.Rect getCropRect();
+ method public long getDataSpace();
method public abstract int getFormat();
method @Nullable public android.hardware.HardwareBuffer getHardwareBuffer();
method public abstract int getHeight();
@@ -21605,6 +21670,7 @@
method public abstract long getTimestamp();
method public abstract int getWidth();
method public void setCropRect(android.graphics.Rect);
+ method public void setDataSpace(long);
method public void setTimestamp(long);
}
@@ -31790,7 +31856,7 @@
method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
- method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+ method @Deprecated @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
method @NonNull public <T> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader, @NonNull Class<T>);
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -43120,7 +43186,7 @@
method public boolean isDataCapable();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
- method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabledForReason(int);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
method public boolean isEmergencyNumber(@NonNull String);
method public boolean isHearingAidCompatibilitySupported();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb8e0c7..ddaa96d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -83,6 +83,7 @@
field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT";
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+ field public static final String CHANGE_APP_LAUNCH_TIME_ESTIMATE = "android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE";
field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
@@ -996,8 +997,12 @@
field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+ field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+ field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+ field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
+ field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -1020,6 +1025,10 @@
field public static final String REQUIRED_APP_MANAGED_DEVICE = "android.app.REQUIRED_APP_MANAGED_DEVICE";
field public static final String REQUIRED_APP_MANAGED_PROFILE = "android.app.REQUIRED_APP_MANAGED_PROFILE";
field public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER";
+ field public static final int RESULT_DEVICE_OWNER_SET = 123; // 0x7b
+ field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1; // 0x1
+ field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; // 0x2
+ field public static final int RESULT_WORK_PROFILE_CREATED = 122; // 0x7a
field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
@@ -1923,6 +1932,8 @@
method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTime(@NonNull String, long);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTimes(@NonNull java.util.Map<java.lang.String,java.lang.Long>);
method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
@@ -9199,6 +9210,7 @@
public final class StorageVolume implements android.os.Parcelable {
method @NonNull public String getId();
+ method public boolean isStub();
}
}
@@ -9762,7 +9774,9 @@
field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
field public static final String MAX_CONNECTIONS = "max_conns";
field public static final String MODEM_PERSIST = "modem_cognitive";
- field public static final String MTU = "mtu";
+ field @Deprecated public static final String MTU = "mtu";
+ field public static final String MTU_V4 = "mtu_v4";
+ field public static final String MTU_V6 = "mtu_v6";
field public static final int NO_APN_SET_ID = 0; // 0x0
field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time";
field public static final int UNEDITED = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c1ab070..6b4d773 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3298,7 +3298,7 @@
method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
- method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+ method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
index 8884508..74c91d6 100644
--- a/core/java/android/accessibilityservice/MagnificationConfig.java
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -16,6 +16,7 @@
package android.accessibilityservice;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
@@ -29,20 +30,21 @@
* magnification.
*
* <p>
- * When the magnification config uses {@link #DEFAULT_MODE},
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_DEFAULT},
* {@link AccessibilityService} will be able to control the activated magnifier on the display.
* If there is no magnifier activated, it controls the last-activated magnification mode.
* If there is no magnifier activated before, it controls full-screen magnifier by default.
* </p>
*
* <p>
- * When the magnification config uses {@link #FULLSCREEN_MODE}. {@link AccessibilityService} will
- * be able to control full-screen magnifier on the display.
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_FULLSCREEN}.
+ * {@link AccessibilityService} will be able to control full-screen magnifier on the display.
* </p>
*
* <p>
- * When the magnification config uses {@link #WINDOW_MODE}. {@link AccessibilityService} will be
- * able to control the activated window magnifier on the display.
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_WINDOW}.
+ * {@link AccessibilityService} will be able to control the activated window magnifier
+ * on the display.
* </p>
*
* <p>
@@ -54,22 +56,23 @@
public final class MagnificationConfig implements Parcelable {
/** The controlling magnification mode. It controls the activated magnifier. */
- public static final int DEFAULT_MODE = 0;
+ public static final int MAGNIFICATION_MODE_DEFAULT = 0;
/** The controlling magnification mode. It controls fullscreen magnifier. */
- public static final int FULLSCREEN_MODE = 1;
+ public static final int MAGNIFICATION_MODE_FULLSCREEN = 1;
/** The controlling magnification mode. It controls window magnifier. */
- public static final int WINDOW_MODE = 2;
+ public static final int MAGNIFICATION_MODE_WINDOW = 2;
+ /** @hide */
@IntDef(prefix = {"MAGNIFICATION_MODE"}, value = {
- DEFAULT_MODE,
- FULLSCREEN_MODE,
- WINDOW_MODE,
+ MAGNIFICATION_MODE_DEFAULT,
+ MAGNIFICATION_MODE_FULLSCREEN,
+ MAGNIFICATION_MODE_WINDOW,
})
@Retention(RetentionPolicy.SOURCE)
- @interface MAGNIFICATION_MODE {
+ @interface MagnificationMode {
}
- private int mMode = DEFAULT_MODE;
+ private int mMode = MAGNIFICATION_MODE_DEFAULT;
private float mScale = Float.NaN;
private float mCenterX = Float.NaN;
private float mCenterY = Float.NaN;
@@ -107,9 +110,9 @@
/**
* Returns the screen-relative X coordinate of the center of the magnification viewport.
*
- * @return the X coordinate. If the controlling magnifier is {@link #WINDOW_MODE} but not
- * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
- * #FULLSCREEN_MODE} but not enabled, it returns 0
+ * @return the X coordinate. If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
+ * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+ * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
*/
public float getCenterX() {
return mCenterX;
@@ -118,9 +121,9 @@
/**
* Returns the screen-relative Y coordinate of the center of the magnification viewport.
*
- * @return the Y coordinate If the controlling magnifier is {@link #WINDOW_MODE} but not
- * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
- * #FULLSCREEN_MODE} but not enabled, it returns 0
+ * @return the Y coordinate If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
+ * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+ * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
*/
public float getCenterY() {
return mCenterY;
@@ -159,7 +162,7 @@
*/
public static final class Builder {
- private int mMode = DEFAULT_MODE;
+ private int mMode = MAGNIFICATION_MODE_DEFAULT;
private float mScale = Float.NaN;
private float mCenterX = Float.NaN;
private float mCenterY = Float.NaN;
@@ -177,7 +180,7 @@
* @return This builder
*/
@NonNull
- public MagnificationConfig.Builder setMode(@MAGNIFICATION_MODE int mode) {
+ public MagnificationConfig.Builder setMode(@MagnificationMode int mode) {
mMode = mode;
return this;
}
@@ -185,20 +188,22 @@
/**
* Sets the magnification scale.
*
- * @param scale The magnification scale
+ * @param scale The magnification scale, in the range [1, 8]
* @return This builder
*/
@NonNull
- public MagnificationConfig.Builder setScale(float scale) {
+ public MagnificationConfig.Builder setScale(@FloatRange(from = 1f, to = 8f) float scale) {
mScale = scale;
return this;
}
/**
* Sets the X coordinate of the center of the magnification viewport.
+ * The controlling magnifier will apply the given position.
*
* @param centerX the screen-relative X coordinate around which to
- * center and scale, or {@link Float#NaN} to leave unchanged
+ * center and scale that is in the range [0, screenWidth],
+ * or {@link Float#NaN} to leave unchanged
* @return This builder
*/
@NonNull
@@ -209,9 +214,11 @@
/**
* Sets the Y coordinate of the center of the magnification viewport.
+ * The controlling magnifier will apply the given position.
*
* @param centerY the screen-relative Y coordinate around which to
- * center and scale, or {@link Float#NaN} to leave unchanged
+ * center and scale that is in the range [0, screenHeight],
+ * or {@link Float#NaN} to leave unchanged
* @return This builder
*/
@NonNull
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cf95ffe..ee74bb8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -470,6 +470,105 @@
= "android.app.action.PROVISION_FINALIZATION";
/**
+ * Activity action: starts the managed profile provisioning flow inside the device management
+ * role holder.
+ *
+ * <p>During the managed profile provisioning flow, the platform-provided provisioning handler
+ * will delegate provisioning to the device management role holder, by firing this intent.
+ * Third-party mobile device management applications attempting to fire this intent will
+ * receive a {@link SecurityException}.
+ *
+ * <p>Device management role holders are required to have a handler for this intent action.
+ *
+ * <p>A result code of {@link Activity#RESULT_OK} implies that managed profile provisioning
+ * finished successfully. If it did not, a result code of {@link Activity#RESULT_CANCELED}
+ * is used instead.
+ *
+ * @see #ACTION_PROVISION_MANAGED_PROFILE
+ *
+ * @hide
+ */
+ // TODO(b/208628038): Uncomment when the permission is in place
+ // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
+ "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+
+ /**
+ * Result code that can be returned by the {@link
+ * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+ * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if a work
+ * profile has been created.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RESULT_WORK_PROFILE_CREATED = 122;
+
+ /**
+ * Result code that can be returned by the {@link
+ * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+ * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if the
+ * device owner was set.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RESULT_DEVICE_OWNER_SET = 123;
+
+ /**
+ * Activity action: starts the trusted source provisioning flow inside the device management
+ * role holder.
+ *
+ * <p>During the trusted source provisioning flow, the platform-provided provisioning handler
+ * will delegate provisioning to the device management role holder, by firing this intent.
+ * Third-party mobile device management applications attempting to fire this intent will
+ * receive a {@link SecurityException}.
+ *
+ * <p>Device management role holders are required to have a handler for this intent action.
+ *
+ * <p>The result codes can be either {@link #RESULT_WORK_PROFILE_CREATED}, {@link
+ * #RESULT_DEVICE_OWNER_SET} or {@link Activity#RESULT_CANCELED} if provisioning failed.
+ *
+ * @see #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE
+ *
+ * @hide
+ */
+ // TODO(b/208628038): Uncomment when the permission is in place
+ // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
+ "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+
+ /**
+ * Activity action: starts the provisioning finalization flow inside the device management
+ * role holder.
+ *
+ * <p>During the provisioning finalization flow, the platform-provided provisioning handler
+ * will delegate provisioning to the device management role holder, by firing this intent.
+ * Third-party mobile device management applications attempting to fire this intent will
+ * receive a {@link SecurityException}.
+ *
+ * <p>Device management role holders are required to have a handler for this intent action.
+ *
+ * <p>This handler forwards the result from the admin app's {@link
+ * #ACTION_ADMIN_POLICY_COMPLIANCE} handler. Result code {@link Activity#RESULT_CANCELED}
+ * implies the provisioning finalization flow has failed.
+ *
+ * @see #ACTION_PROVISION_FINALIZATION
+ *
+ * @hide
+ */
+ // TODO(b/208628038): Uncomment when the permission is in place
+ // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
+ "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+
+ /**
* Action: Bugreport sharing with device owner has been accepted by the user.
*
* @hide
@@ -2821,6 +2920,43 @@
"android.app.action.ADMIN_POLICY_COMPLIANCE";
/**
+ * Activity action: Starts the device management role holder updater.
+ *
+ * <p>The activity must handle the device management role holder update and set the intent
+ * result to either {@link Activity#RESULT_OK} if the update was successful, {@link
+ * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a problem
+ * that may be solved by relaunching it again, or {@link
+ * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem
+ * that will not be solved by relaunching it again.
+ *
+ * @hide
+ */
+ // TODO(b/208628038): Uncomment when the permission is in place
+ // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
+ "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+
+ /**
+ * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+ * handler if it encounters a problem that may be solved by relaunching it again.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1;
+
+ /**
+ * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+ * handler if it encounters a problem that will not be solved by relaunching it again.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2;
+
+ /**
* Maximum supported password length. Kind-of arbitrary.
* @hide
*/
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index ceab02f..aac23d8 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -103,6 +103,14 @@
String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
/**
+ * A shell command that dumps a {@link
+ * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for
+ * debugging.
+ * @hide
+ */
+ String SHELL_COMMAND_DUMP_METRICS = "dump_metrics";
+
+ /**
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
*
* @hide
diff --git a/core/java/android/app/usage/AppLaunchEstimateInfo.java b/core/java/android/app/usage/AppLaunchEstimateInfo.java
new file mode 100644
index 0000000..2085d00
--- /dev/null
+++ b/core/java/android/app/usage/AppLaunchEstimateInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A pair of {package, estimated launch time} to denote the estimated launch time for a given
+ * package.
+ * Used as a vehicle of data across the binder IPC.
+ *
+ * @hide
+ */
+public final class AppLaunchEstimateInfo implements Parcelable {
+
+ public final String packageName;
+ @CurrentTimeMillisLong
+ public final long estimatedLaunchTime;
+
+ private AppLaunchEstimateInfo(Parcel in) {
+ packageName = in.readString();
+ estimatedLaunchTime = in.readLong();
+ }
+
+ public AppLaunchEstimateInfo(String packageName,
+ @CurrentTimeMillisLong long estimatedLaunchTime) {
+ this.packageName = packageName;
+ this.estimatedLaunchTime = estimatedLaunchTime;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(packageName);
+ dest.writeLong(estimatedLaunchTime);
+ }
+
+ @NonNull
+ public static final Creator<AppLaunchEstimateInfo> CREATOR =
+ new Creator<AppLaunchEstimateInfo>() {
+ @Override
+ public AppLaunchEstimateInfo createFromParcel(Parcel source) {
+ return new AppLaunchEstimateInfo(source);
+ }
+
+ @Override
+ public AppLaunchEstimateInfo[] newArray(int size) {
+ return new AppLaunchEstimateInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 585eb61..170d766 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -51,6 +51,8 @@
void setAppStandbyBucket(String packageName, int bucket, int userId);
ParceledListSlice getAppStandbyBuckets(String callingPackage, int userId);
void setAppStandbyBuckets(in ParceledListSlice appBuckets, int userId);
+ void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime, int userId);
+ void setEstimatedLaunchTimes(in ParceledListSlice appLaunchTimes, int userId);
void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs,
in PendingIntent callback, String callingPackage);
void unregisterAppUsageObserver(int observerId, String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index ac7a318..3cbb24b 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -16,6 +16,7 @@
package android.app.usage;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -772,6 +773,72 @@
}
/**
+ * Changes an app's estimated launch time. An app is considered "launched" when a user opens
+ * one of its {@link android.app.Activity Activities}. The provided time is persisted across
+ * reboots and is used unless 1) the time is more than a week in the future and the platform
+ * thinks the app will be launched sooner, 2) the estimated time has passed. Passing in
+ * {@link Long#MAX_VALUE} effectively clears the previously set launch time for the app.
+ *
+ * @param packageName The package name of the app to set the bucket for.
+ * @param estimatedLaunchTime The next time the app is expected to be launched. Units are in
+ * milliseconds since epoch (the same as
+ * {@link System#currentTimeMillis()}).
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
+ public void setEstimatedLaunchTime(@NonNull String packageName,
+ @CurrentTimeMillisLong long estimatedLaunchTime) {
+ if (packageName == null) {
+ throw new NullPointerException("package name cannot be null");
+ }
+ if (estimatedLaunchTime <= 0) {
+ throw new IllegalArgumentException("estimated launch time must be positive");
+ }
+ try {
+ mService.setEstimatedLaunchTime(packageName, estimatedLaunchTime, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Changes the estimated launch times for multiple apps at once. The map is keyed by the
+ * package name and the value is the estimated launch time.
+ *
+ * @param estimatedLaunchTimes A map of package name to estimated launch time.
+ * @see #setEstimatedLaunchTime(String, long)
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
+ public void setEstimatedLaunchTimes(@NonNull Map<String, Long> estimatedLaunchTimes) {
+ if (estimatedLaunchTimes == null) {
+ throw new NullPointerException("estimatedLaunchTimes cannot be null");
+ }
+ final List<AppLaunchEstimateInfo> estimateList =
+ new ArrayList<>(estimatedLaunchTimes.size());
+ for (Map.Entry<String, Long> estimateEntry : estimatedLaunchTimes.entrySet()) {
+ final String pkgName = estimateEntry.getKey();
+ if (pkgName == null) {
+ throw new NullPointerException("package name cannot be null");
+ }
+ final Long estimatedLaunchTime = estimateEntry.getValue();
+ if (estimatedLaunchTime == null || estimatedLaunchTime <= 0) {
+ throw new IllegalArgumentException("estimated launch time must be positive");
+ }
+ estimateList.add(new AppLaunchEstimateInfo(pkgName, estimatedLaunchTime));
+ }
+ final ParceledListSlice<AppLaunchEstimateInfo> slice =
+ new ParceledListSlice<>(estimateList);
+ try {
+ mService.setEstimatedLaunchTimes(slice, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Register an app usage limit observer that receives a callback on the provided intent when
* the sum of usages of apps and tokens in the {@code observed} array exceeds the
diff --git a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java
new file mode 100644
index 0000000..dcaf4b6
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents the codec configuration for a Bluetooth LE Audio source device.
+ * <p>Contains the source codec type.
+ * <p>The source codec type values are the same as those supported by the
+ * device hardware.
+ *
+ * {@see BluetoothLeAudioCodecConfig}
+ */
+public final class BluetoothLeAudioCodecConfig {
+ // Add an entry for each source codec here.
+
+ /** @hide */
+ @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {
+ SOURCE_CODEC_TYPE_LC3,
+ SOURCE_CODEC_TYPE_INVALID
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SourceCodecType {};
+
+ public static final int SOURCE_CODEC_TYPE_LC3 = 0;
+ public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
+
+ /**
+ * Represents the count of valid source codec types. Can be accessed via
+ * {@link #getMaxCodecType}.
+ */
+ private static final int SOURCE_CODEC_TYPE_MAX = 1;
+
+ private final @SourceCodecType int mCodecType;
+
+ /**
+ * Creates a new BluetoothLeAudioCodecConfig.
+ *
+ * @param codecType the source codec type
+ */
+ private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) {
+ mCodecType = codecType;
+ }
+
+ @Override
+ public String toString() {
+ return "{codecName:" + getCodecName() + "}";
+ }
+
+ /**
+ * Gets the codec type.
+ *
+ * @return the codec type
+ */
+ public @SourceCodecType int getCodecType() {
+ return mCodecType;
+ }
+
+ /**
+ * Returns the valid codec types count.
+ */
+ public static int getMaxCodecType() {
+ return SOURCE_CODEC_TYPE_MAX;
+ }
+
+ /**
+ * Gets the codec name.
+ *
+ * @return the codec name
+ */
+ public @NonNull String getCodecName() {
+ switch (mCodecType) {
+ case SOURCE_CODEC_TYPE_LC3:
+ return "LC3";
+ case SOURCE_CODEC_TYPE_INVALID:
+ return "INVALID CODEC";
+ default:
+ break;
+ }
+ return "UNKNOWN CODEC(" + mCodecType + ")";
+ }
+
+ /**
+ * Builder for {@link BluetoothLeAudioCodecConfig}.
+ * <p> By default, the codec type will be set to
+ * {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID}
+ */
+ public static final class Builder {
+ private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID;
+
+ /**
+ * Set codec type for Bluetooth codec config.
+ *
+ * @param codecType of this codec
+ * @return the same Builder instance
+ */
+ public @NonNull Builder setCodecType(@SourceCodecType int codecType) {
+ mCodecType = codecType;
+ return this;
+ }
+
+ /**
+ * Build {@link BluetoothLeAudioCodecConfig}.
+ * @return new BluetoothLeAudioCodecConfig built
+ */
+ public @NonNull BluetoothLeAudioCodecConfig build() {
+ return new BluetoothLeAudioCodecConfig(mCodecType);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 0cf9f9f..4f2dba75 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -436,6 +436,8 @@
return "OPP";
case HEARING_AID:
return "HEARING_AID";
+ case LE_AUDIO:
+ return "LE_AUDIO";
default:
return "UNKNOWN_PROFILE";
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 1dc161c..6e1f8b5 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -119,18 +119,18 @@
* address, bonded devices are also searched among. This allows to obtain the necessary app
* privileges even if the device is already paired.
*/
- private boolean mSingleDevice = false;
+ private final boolean mSingleDevice;
/**
* If set, only devices matching either of the given filters will be shown to the user
*/
@DataClass.PluralOf("deviceFilter")
- private @NonNull List<DeviceFilter<?>> mDeviceFilters = new ArrayList<>();
+ private final @NonNull List<DeviceFilter<?>> mDeviceFilters;
/**
- * If set, association will be requested as a corresponding kind of device
+ * Profile of the device.
*/
- private @Nullable @DeviceProfile String mDeviceProfile = null;
+ private final @Nullable @DeviceProfile String mDeviceProfile;
/**
* The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
@@ -157,7 +157,7 @@
*
* @hide
*/
- private @Nullable String mCallingPackage = null;
+ private @Nullable String mCallingPackage;
/**
* The user-readable description of the device profile's privileges.
@@ -166,7 +166,7 @@
*
* @hide
*/
- private @Nullable String mDeviceProfilePrivilegesDescription = null;
+ private @Nullable String mDeviceProfilePrivilegesDescription;
/**
* The time at which his request was created
@@ -182,7 +182,22 @@
*
* @hide
*/
- private boolean mSkipPrompt = false;
+ private boolean mSkipPrompt;
+
+ /**
+ * @return profile of the companion device.
+ */
+ public @Nullable @DeviceProfile String getDeviceProfile() {
+ return mDeviceProfile;
+ }
+
+ /**
+ * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+ * "self-managed" association.
+ */
+ public @Nullable CharSequence getDisplayName() {
+ return mDisplayName;
+ }
/**
* Whether the association is to be managed by the companion application.
@@ -210,6 +225,17 @@
return mForceConfirmation;
}
+ /**
+ * 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.
+ */
+ public boolean isSingleDevice() {
+ return mSingleDevice;
+ }
+
/** @hide */
public void setCallingPackage(@NonNull String pkg) {
mCallingPackage = pkg;
@@ -226,12 +252,6 @@
}
/** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public boolean isSingleDevice() {
- return mSingleDevice;
- }
-
- /** @hide */
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public List<DeviceFilter<?>> getDeviceFilters() {
@@ -386,7 +406,7 @@
* @param deviceFilters
* If set, only devices matching either of the given filters will be shown to the user
* @param deviceProfile
- * If set, association will be requested as a corresponding kind of device
+ * 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.
@@ -443,27 +463,6 @@
}
/**
- * If set, association will be requested as a corresponding kind of device
- *
- * @hide
- */
- @DataClass.Generated.Member
- public @Nullable @DeviceProfile String getDeviceProfile() {
- return mDeviceProfile;
- }
-
- /**
- * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
- * "self-managed" association.
- *
- * @hide
- */
- @DataClass.Generated.Member
- public @Nullable CharSequence getDisplayName() {
- return mDisplayName;
- }
-
- /**
* The app package making the request.
*
* Populated by the system.
@@ -655,10 +654,10 @@
};
@DataClass.Generated(
- time = 1637228802427L,
+ time = 1638368698639L,
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 boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @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.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\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 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)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
new file mode 100644
index 0000000..65383c5
--- /dev/null
+++ b/core/java/android/hardware/DataSpace.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware;
+
+import android.annotation.LongDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DataSpace identifies three components of colors - standard (primaries), transfer and range.
+ *
+ * <p>A DataSpace describes how buffer data, such as from an {@link android.media.Image Image}
+ * or a {@link android.hardware.HardwareBuffer HardwareBuffer}
+ * should be interpreted by both applications and typical hardware.</p>
+ *
+ * <p>As buffer information is not guaranteed to be representative of color information,
+ * while DataSpace is typically used to describe three aspects of interpreting colors,
+ * some DataSpaces may describe other typical interpretations of buffer data
+ * such as depth information.</p>
+ *
+ * <p>Note that while {@link android.graphics.ColorSpace ColorSpace} and {@code DataSpace}
+ * are similar concepts, they are not equivalent. Not all ColorSpaces,
+ * such as {@link android.graphics.ColorSpace.Named#ACES ColorSpace.Named.ACES},
+ * are able to be understood by typical hardware blocks so they cannot be DataSpaces.</p>
+ *
+ * <h3>Standard aspect</h3>
+ *
+ * <p>Defines the chromaticity coordinates of the source primaries in terms of
+ * the CIE 1931 definition of x and y specified in ISO 11664-1.</p>
+ *
+ * <h3>Transfer aspect</h3>
+ *
+ * <p>Transfer characteristics are the opto-electronic transfer characteristic
+ * at the source as a function of linear optical intensity (luminance).</p>
+ *
+ * <p>For digital signals, E corresponds to the recorded value. Normally, the
+ * transfer function is applied in RGB space to each of the R, G and B
+ * components independently. This may result in color shift that can be
+ * minized by applying the transfer function in Lab space only for the L
+ * component. Implementation may apply the transfer function in RGB space
+ * for all pixel formats if desired.</p>
+ *
+ * <h3>Range aspect</h3>
+ *
+ * <p>Defines the range of values corresponding to the unit range of {@code 0-1}.</p>
+ */
+
+public final class DataSpace {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, value = {
+ STANDARD_UNSPECIFIED,
+ STANDARD_BT709,
+ STANDARD_BT601_625,
+ STANDARD_BT601_625_UNADJUSTED,
+ STANDARD_BT601_525,
+ STANDARD_BT601_525_UNADJUSTED,
+ STANDARD_BT2020,
+ STANDARD_BT2020_CONSTANT_LUMINANCE,
+ STANDARD_BT470M,
+ STANDARD_FILM,
+ STANDARD_DCI_P3,
+ STANDARD_ADOBE_RGB
+ })
+ public @interface DataSpaceStandard {};
+
+ private static final long STANDARD_MASK = 63 << 16;
+
+ /**
+ * Chromacity coordinates are unknown or are determined by the application.
+ */
+ public static final long STANDARD_UNSPECIFIED = 0 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.2126}, {@code KB = 0.0722} luminance interpretation
+ * for RGB conversion.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.300 0.600
+ * blue 0.150 0.060
+ * red 0.640 0.330
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT709 = 1 << 16;
+ /**
+ * Use the adjusted {@code KR = 0.299}, {@code KB = 0.114} luminance interpretation
+ * for RGB conversion from the one purely determined by the primaries
+ * to minimize the color shift into RGB space that uses BT.709
+ * primaries.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.290 0.600
+ * blue 0.150 0.060
+ * red 0.640 0.330
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT601_625 = 2 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.222}, {@code KB = 0.071} luminance interpretation
+ * for RGB conversion.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.290 0.600
+ * blue 0.150 0.060
+ * red 0.640 0.330
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT601_625_UNADJUSTED = 3 << 16;
+ /**
+ * Use the adjusted {@code KR = 0.299}, {@code KB = 0.114} luminance interpretation
+ * for RGB conversion from the one purely determined by the primaries
+ * to minimize the color shift into RGB space that uses BT.709
+ * primaries.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.310 0.595
+ * blue 0.155 0.070
+ * red 0.630 0.340
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT601_525 = 4 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.212}, {@code KB = 0.087} luminance interpretation
+ * for RGB conversion (as in SMPTE 240M).
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.310 0.595
+ * blue 0.155 0.070
+ * red 0.630 0.340
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT601_525_UNADJUSTED = 5 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.2627}, {@code KB = 0.0593} luminance interpretation
+ * for RGB conversion.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.170 0.797
+ * blue 0.131 0.046
+ * red 0.708 0.292
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT2020 = 6 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.2627}, {@code KB = 0.0593} luminance interpretation
+ * for RGB conversion using the linear domain.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.170 0.797
+ * blue 0.131 0.046
+ * red 0.708 0.292
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.30}, {@code KB = 0.11} luminance interpretation
+ * for RGB conversion.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.21 0.71
+ * blue 0.14 0.08
+ * red 0.67 0.33
+ * white (C) 0.310 0.316 </pre>
+ */
+ public static final long STANDARD_BT470M = 8 << 16;
+ /**
+ * Use the unadjusted {@code KR = 0.254}, {@code KB = 0.068} luminance interpretation
+ * for RGB conversion.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.243 0.692
+ * blue 0.145 0.049
+ * red 0.681 0.319
+ * white (C) 0.310 0.316 </pre>
+ */
+ public static final long STANDARD_FILM = 9 << 16;
+ /**
+ * SMPTE EG 432-1 and SMPTE RP 431-2.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.265 0.690
+ * blue 0.150 0.060
+ * red 0.680 0.320
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_DCI_P3 = 10 << 16;
+ /**
+ * Adobe RGB primaries.
+ *
+ * <pre>
+ * Primaries: x y
+ * green 0.210 0.710
+ * blue 0.150 0.060
+ * red 0.640 0.330
+ * white (D65) 0.3127 0.3290 </pre>
+ */
+ public static final long STANDARD_ADOBE_RGB = 11 << 16;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, value = {
+ TRANSFER_UNSPECIFIED,
+ TRANSFER_LINEAR,
+ TRANSFER_SRGB,
+ TRANSFER_SMPTE_170M,
+ TRANSFER_GAMMA2_2,
+ TRANSFER_GAMMA2_6,
+ TRANSFER_GAMMA2_8,
+ TRANSFER_ST2084,
+ TRANSFER_HLG
+ })
+ public @interface DataSpaceTransfer {};
+
+ private static final long TRANSFER_MASK = 31 << 22;
+
+ /**
+ * Transfer characteristics are unknown or are determined by the
+ * application.
+ */
+ public static final long TRANSFER_UNSPECIFIED = 0 << 22;
+ /**
+ * Linear transfer.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = L
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal}</pre>
+ */
+ public static final long TRANSFER_LINEAR = 1 << 22;
+ /**
+ * sRGB transfer.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = 1.055 * L^(1/2.4) - 0.055 for 0.0031308 <= L <= 1
+ * = 12.92 * L for 0 <= L < 0.0031308
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal}</pre>
+ *
+ * Use for RGB formats.
+ */
+ public static final long TRANSFER_SRGB = 2 << 22;
+ /**
+ * SMPTE 170M transfer.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = 1.099 * L ^ 0.45 - 0.099 for 0.018 <= L <= 1
+ * = 4.500 * L for 0 <= L < 0.018
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal}</pre>
+ *
+ * Use for YCbCr formats.
+ */
+ public static final long TRANSFER_SMPTE_170M = 3 << 22;
+ /**
+ * Display gamma 2.2.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = L ^ (1/2.2)
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal}</pre>
+ */
+ public static final long TRANSFER_GAMMA2_2 = 4 << 22;
+ /**
+ * Display gamma 2.6.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = L ^ (1/2.6)
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal}</pre>
+ */
+ public static final long TRANSFER_GAMMA2_6 = 5 << 22;
+ /**
+ * Display gamma 2.8.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = L ^ (1/2.8)
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal}</pre>
+ */
+ public static final long TRANSFER_GAMMA2_8 = 6 << 22;
+ /**
+ * SMPTE ST 2084 (Dolby Perceptual Quantizer).
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m
+ * c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375
+ * c2 = 32 * 2413 / 4096 = 18.8515625
+ * c3 = 32 * 2392 / 4096 = 18.6875
+ * m = 128 * 2523 / 4096 = 78.84375
+ * n = 0.25 * 2610 / 4096 = 0.1593017578125
+ * L - luminance of image 0 <= L <= 1 for HDR colorimetry.
+ * L = 1 corresponds to 10000 cd/m2
+ * E - corresponding electrical signal}</pre>
+ */
+ public static final long TRANSFER_ST2084 = 7 << 22;
+ /**
+ * ARIB STD-B67 Hybrid Log Gamma.
+ *
+ * <pre>{@code
+ * Transfer characteristic curve:
+ * E = r * L^0.5 for 0 <= L <= 1
+ * = a * ln(L - b) + c for 1 < L
+ * a = 0.17883277
+ * b = 0.28466892
+ * c = 0.55991073
+ * r = 0.5
+ * L - luminance of image 0 <= L for HDR colorimetry. L = 1 corresponds
+ * to reference white level of 100 cd/m2
+ * E - corresponding electrical signal}</pre>
+ */
+ public static final long TRANSFER_HLG = 8 << 22;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, value = {
+ RANGE_UNSPECIFIED,
+ RANGE_FULL,
+ RANGE_LIMITED,
+ RANGE_EXTENDED
+ })
+ public @interface DataSpaceRange {};
+
+ private static final long RANGE_MASK = 7 << 27;
+
+ /**
+ * Range characteristics are unknown or are determined by the application.
+ */
+ public static final long RANGE_UNSPECIFIED = 0 << 27;
+ /**
+ * Full range uses all values for Y, Cb and Cr from
+ * {@code 0} to {@code 2^b-1}, where b is the bit depth of the color format.
+ */
+ public static final long RANGE_FULL = 1 << 27;
+ /**
+ * Limited range uses values {@code 16/256*2^b} to {@code 235/256*2^b} for Y, and
+ * {@code 1/16*2^b} to {@code 15/16*2^b} for Cb, Cr, R, G and B, where b is the bit depth of
+ * the color format.
+ *
+ * <p>E.g. For 8-bit-depth formats:
+ * Luma (Y) samples should range from 16 to 235, inclusive
+ * Chroma (Cb, Cr) samples should range from 16 to 240, inclusive
+ *
+ * For 10-bit-depth formats:
+ * Luma (Y) samples should range from 64 to 940, inclusive
+ * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive. </p>
+ */
+ public static final long RANGE_LIMITED = 2 << 27;
+ /**
+ * Extended range is used for scRGB only.
+ *
+ * <p>Intended for use with floating point pixel formats. [0.0 - 1.0] is the standard
+ * sRGB space. Values outside the range [0.0 - 1.0] can encode
+ * color outside the sRGB gamut. [-0.5, 7.5] is the scRGB range.
+ * Used to blend/merge multiple dataspaces on a single display.</p>
+ */
+ public static final long RANGE_EXTENDED = 3 << 27;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, value = {
+ DATASPACE_UNKNOWN,
+ DATASPACE_SCRGB_LINEAR,
+ DATASPACE_SRGB,
+ DATASPACE_SCRGB,
+ DATASPACE_DISPLAY_P3,
+ DATASPACE_BT2020_PQ,
+ DATASPACE_ADOBE_RGB,
+ DATASPACE_JFIF,
+ DATASPACE_BT601_625,
+ DATASPACE_BT601_525,
+ DATASPACE_BT2020,
+ DATASPACE_BT709,
+ DATASPACE_DCI_P3,
+ DATASPACE_SRGB_LINEAR
+ })
+ public @interface NamedDataSpace {};
+
+ /**
+ * Default-assumption data space, when not explicitly specified.
+ *
+ * <p>It is safest to assume a buffer is an image with sRGB primaries and
+ * encoding ranges, but the consumer and/or the producer of the data may
+ * simply be using defaults. No automatic gamma transform should be
+ * expected, except for a possible display gamma transform when drawn to a
+ * screen.</p>
+ */
+ public static final long DATASPACE_UNKNOWN = 0;
+ /**
+ * scRGB linear encoding.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT709
+ * Transfer: TRANSFER_LINEAR
+ * Range: RANGE_EXTENDED</pre>
+ *
+ * The values are floating point.
+ * A pixel value of 1.0, 1.0, 1.0 corresponds to sRGB white (D65) at 80 nits.
+ * Values beyond the range [0.0 - 1.0] would correspond to other colors
+ * spaces and/or HDR content.
+ */
+ public static final long DATASPACE_SCRGB_LINEAR = 406913024;
+ /**
+ * sRGB gamma encoding.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT709
+ * Transfer: TRANSFER_SRGB
+ * Range: RANGE_FULL</pre>
+ *
+ * When written, the inverse transformation is performed.
+ *
+ * The alpha component, if present, is always stored in linear space and
+ * is left unmodified when read or written.
+ */
+ public static final long DATASPACE_SRGB = 142671872;
+ /**
+ * scRGB gamma encoding.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT709
+ * Transfer: TRANSFER_SRGB
+ * Range: RANGE_EXTENDED</pre>
+ *
+ * The values are floating point.
+ *
+ * A pixel value of 1.0, 1.0, 1.0 corresponds to sRGB white (D65) at 80 nits.
+ * Values beyond the range [0.0 - 1.0] would correspond to other colors
+ * spaces and/or HDR content.
+ */
+ public static final long DATASPACE_SCRGB = 411107328;
+ /**
+ * Display P3 encoding.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_DCI_P3
+ * Transfer: TRANSFER_SRGB
+ * Range: RANGE_FULL</pre>
+ */
+ public static final long DATASPACE_DISPLAY_P3 = 143261696;
+ /**
+ * ITU-R Recommendation 2020 (BT.2020)
+ *
+ * Ultra High-definition television.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT2020
+ * Transfer: TRANSFER_ST2084
+ * Range: RANGE_FULL</pre>
+ */
+ public static final long DATASPACE_BT2020_PQ = 163971072;
+ /**
+ * Adobe RGB encoding.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_ADOBE_RGB
+ * Transfer: TRANSFER_GAMMA2_2
+ * Range: RANGE_FULL</pre>
+ *
+ * Note: Application is responsible for gamma encoding the data.
+ */
+ public static final long DATASPACE_ADOBE_RGB = 151715840;
+ /**
+ * JPEG File Interchange Format (JFIF).
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT601_625
+ * Transfer: TRANSFER_SMPTE_170M
+ * Range: RANGE_FULL</pre>
+ *
+ * Same model as BT.601-625, but all values (Y, Cb, Cr) range from {@code 0} to {@code 255}
+ */
+ public static final long DATASPACE_JFIF = 146931712;
+ /**
+ * ITU-R Recommendation 601 (BT.601) - 525-line
+ *
+ * Standard-definition television, 525 Lines (NTSC).
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT601_625
+ * Transfer: TRANSFER_SMPTE_170M
+ * Range: RANGE_LIMITED</pre>
+ */
+ public static final long DATASPACE_BT601_625 = 281149440;
+ /**
+ * ITU-R Recommendation 709 (BT.709)
+ *
+ * High-definition television.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT601_525
+ * Transfer: TRANSFER_SMPTE_170M
+ * Range: RANGE_LIMITED</pre>
+ */
+ public static final long DATASPACE_BT601_525 = 281280512;
+ /**
+ * ITU-R Recommendation 2020 (BT.2020)
+ *
+ * Ultra High-definition television.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT2020
+ * Transfer: TRANSFER_SMPTE_170M
+ * Range: RANGE_FULL</pre>
+ */
+ public static final long DATASPACE_BT2020 = 147193856;
+ /**
+ * ITU-R Recommendation 709 (BT.709)
+ *
+ * High-definition television.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT709
+ * Transfer: TRANSFER_SMPTE_170M
+ * Range: RANGE_LIMITED</pre>
+ */
+ public static final long DATASPACE_BT709 = 281083904;
+ /**
+ * SMPTE EG 432-1 and SMPTE RP 431-2
+ *
+ * Digital Cinema DCI-P3.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_DCI_P3
+ * Transfer: TRANSFER_GAMMA2_6
+ * Range: RANGE_FULL</pre>
+ *
+ * Note: Application is responsible for gamma encoding the data as
+ * a 2.6 gamma encoding is not supported in HW.
+ */
+ public static final long DATASPACE_DCI_P3 = 155844608;
+ /**
+ * sRGB linear encoding.
+ *
+ * <p>Composed of the following -</p>
+ * <pre>
+ * Primaries: STANDARD_BT709
+ * Transfer: TRANSFER_LINEAR
+ * Range: RANGE_FULL</pre>
+ *
+ * The values are encoded using the full range ([0,255] for 8-bit) for all
+ * components.
+ */
+ public static final long DATASPACE_SRGB_LINEAR = 138477568;
+
+ private DataSpace() {}
+
+ /**
+ * Pack the dataSpace value using standard, transfer and range field value.
+ * Field values should be in the correct bits place.
+ *
+ * @param standard Chromaticity coordinates of source primaries
+ * @param transfer Opto-electronic transfer characteristic at the source
+ * @param range The range of values
+ *
+ * @return The long dataspace packed by standard, transfer and range value
+ */
+ public static @NamedDataSpace long pack(@DataSpaceStandard long standard,
+ @DataSpaceTransfer long transfer,
+ @DataSpaceRange long range) {
+ if ((standard & STANDARD_MASK) != standard) {
+ throw new IllegalArgumentException("Invalid standard " + standard);
+ }
+ if ((transfer & TRANSFER_MASK) != transfer) {
+ throw new IllegalArgumentException("Invalid transfer " + transfer);
+ }
+ if ((range & RANGE_MASK) != range) {
+ throw new IllegalArgumentException("Invalid range " + range);
+ }
+ return standard | transfer | range;
+ }
+
+ /**
+ * Unpack the standard field value from the packed dataSpace value.
+ *
+ * @param dataSpace The packed dataspace value
+ *
+ * @return The standard aspect
+ */
+ public static @DataSpaceStandard long getStandard(@NamedDataSpace long dataSpace) {
+ @DataSpaceStandard long standard = dataSpace & STANDARD_MASK;
+ return standard;
+ }
+
+ /**
+ * Unpack the transfer field value from the packed dataSpace value
+ *
+ * @param dataSpace The packed dataspace value
+ *
+ * @return The transfer aspect
+ */
+ public static @DataSpaceTransfer long getTransfer(@NamedDataSpace long dataSpace) {
+ @DataSpaceTransfer long transfer = dataSpace & TRANSFER_MASK;
+ return transfer;
+ }
+
+ /**
+ * Unpack the range field value from the packed dataSpace value
+ *
+ * @param dataSpace The packed dataspace value
+ *
+ * @return The range aspect
+ */
+ public static @DataSpaceRange long getRange(@NamedDataSpace long dataSpace) {
+ @DataSpaceRange long range = dataSpace & RANGE_MASK;
+ return range;
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index cf914c5..551f512 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,10 +49,10 @@
* Defines behavior in response to state update
* @param newState new state of fingerprint sensor
*/
- public void onStateChanged(@FingerprintStateListener.State int newState) {};
+ public void onStateChanged(@FingerprintStateListener.State int newState) {}
/**
* Invoked when enrollment state changes for the specified user
*/
- public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {}
}
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..6b33e4f
--- /dev/null
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+// TODO: Add documents
+/** @hide */
+public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority {
+ private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
+ @NonNull private final Set<String> mAllowedNetworkPlmnIds;
+ private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
+ @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
+
+ private static final String ALLOW_ROAMING_KEY = "mAllowRoaming";
+ private final boolean mAllowRoaming;
+
+ private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic";
+ private final boolean mRequireOpportunistic;
+
+ private VcnCellUnderlyingNetworkPriority(
+ int networkQuality,
+ boolean allowMetered,
+ Set<String> allowedNetworkPlmnIds,
+ Set<Integer> allowedSpecificCarrierIds,
+ boolean allowRoaming,
+ boolean requireOpportunistic) {
+ super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered);
+ mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
+ mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
+ mAllowRoaming = allowRoaming;
+ mRequireOpportunistic = requireOpportunistic;
+
+ validate();
+ }
+
+ /** @hide */
+ @Override
+ protected void validate() {
+ super.validate();
+ validatePlmnIds(mAllowedNetworkPlmnIds);
+ Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null");
+ }
+
+ private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) {
+ Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null");
+
+ // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
+ // digits.
+ for (String id : allowedNetworkPlmnIds) {
+ if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
+ continue;
+ } else {
+ throw new IllegalArgumentException("Found invalid PLMN ID: " + id);
+ }
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ public static VcnCellUnderlyingNetworkPriority fromPersistableBundle(
+ @NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle is null");
+
+ final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
+ final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+
+ final PersistableBundle plmnIdsBundle =
+ in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
+ Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null");
+ final Set<String> allowedNetworkPlmnIds =
+ new ArraySet<String>(
+ PersistableBundleUtils.toList(plmnIdsBundle, STRING_DESERIALIZER));
+
+ final PersistableBundle specificCarrierIdsBundle =
+ in.getPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY);
+ Objects.requireNonNull(specificCarrierIdsBundle, "specificCarrierIdsBundle is null");
+ final Set<Integer> allowedSpecificCarrierIds =
+ new ArraySet<Integer>(
+ PersistableBundleUtils.toList(
+ specificCarrierIdsBundle, INTEGER_DESERIALIZER));
+
+ final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY);
+ final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY);
+
+ return new VcnCellUnderlyingNetworkPriority(
+ networkQuality,
+ allowMetered,
+ allowedNetworkPlmnIds,
+ allowedSpecificCarrierIds,
+ allowRoaming,
+ requireOpportunistic);
+ }
+
+ /** @hide */
+ @Override
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ public PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = super.toPersistableBundle();
+
+ final PersistableBundle plmnIdsBundle =
+ PersistableBundleUtils.fromList(
+ new ArrayList<>(mAllowedNetworkPlmnIds), STRING_SERIALIZER);
+ result.putPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY, plmnIdsBundle);
+
+ final PersistableBundle specificCarrierIdsBundle =
+ PersistableBundleUtils.fromList(
+ new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
+ result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
+
+ result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming);
+ result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic);
+
+ return result;
+ }
+
+ /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
+ @NonNull
+ public Set<String> getAllowedPlmnIds() {
+ return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
+ }
+
+ /**
+ * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is
+ * acceptable.
+ */
+ @NonNull
+ public Set<Integer> getAllowedSpecificCarrierIds() {
+ return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
+ }
+
+ /** Return if roaming is allowed. */
+ public boolean allowRoaming() {
+ return mAllowRoaming;
+ }
+
+ /** Return if requiring an opportunistic network. */
+ public boolean requireOpportunistic() {
+ return mRequireOpportunistic;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ super.hashCode(),
+ mAllowedNetworkPlmnIds,
+ mAllowedSpecificCarrierIds,
+ mAllowRoaming,
+ mRequireOpportunistic);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!super.equals(other)) {
+ return false;
+ }
+
+ if (!(other instanceof VcnCellUnderlyingNetworkPriority)) {
+ return false;
+ }
+
+ final VcnCellUnderlyingNetworkPriority rhs = (VcnCellUnderlyingNetworkPriority) other;
+ return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
+ && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
+ && mAllowRoaming == rhs.mAllowRoaming
+ && mRequireOpportunistic == rhs.mRequireOpportunistic;
+ }
+
+ /** This class is used to incrementally build WifiNetworkPriority objects. */
+ public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+ @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
+ @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
+
+ private boolean mAllowRoaming = false;
+ private boolean mRequireOpportunistic = false;
+
+ /** Construct a Builder object. */
+ public Builder() {}
+
+ /**
+ * Set allowed operator PLMN IDs.
+ *
+ * <p>This is used to distinguish cases where roaming agreements may dictate a different
+ * priority from a partner's networks.
+ *
+ * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an
+ * empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and
+ * thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()}
+ * and {@link SubscriptionInfo#getMncString()}.
+ */
+ @NonNull
+ public Builder setAllowedPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
+ validatePlmnIds(allowedNetworkPlmnIds);
+
+ mAllowedNetworkPlmnIds.clear();
+ mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds);
+ return this;
+ }
+
+ /**
+ * Set allowed specific carrier IDs.
+ *
+ * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty
+ * set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}.
+ */
+ @NonNull
+ public Builder setAllowedSpecificCarrierIds(
+ @NonNull Set<Integer> allowedSpecificCarrierIds) {
+ Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null");
+ mAllowedSpecificCarrierIds.clear();
+ mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds);
+ return this;
+ }
+
+ /**
+ * Set if roaming is allowed.
+ *
+ * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code
+ * false}.
+ */
+ @NonNull
+ public Builder setAllowRoaming(boolean allowRoaming) {
+ mAllowRoaming = allowRoaming;
+ return this;
+ }
+
+ /**
+ * Set if requiring an opportunistic network.
+ *
+ * @param requireOpportunistic the flag to indicate if caller requires an opportunistic
+ * network. Defaults to {@code false}.
+ */
+ @NonNull
+ public Builder setRequireOpportunistic(boolean requireOpportunistic) {
+ mRequireOpportunistic = requireOpportunistic;
+ return this;
+ }
+
+ /** Build the VcnCellUnderlyingNetworkPriority. */
+ @NonNull
+ public VcnCellUnderlyingNetworkPriority build() {
+ return new VcnCellUnderlyingNetworkPriority(
+ mNetworkQuality,
+ mAllowMetered,
+ mAllowedNetworkPlmnIds,
+ mAllowedSpecificCarrierIds,
+ mAllowRoaming,
+ mRequireOpportunistic);
+ }
+
+ /** @hide */
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 752ef3e..de4ada2 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
package android.net.vcn;
import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -41,6 +42,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
@@ -157,6 +159,34 @@
TimeUnit.MINUTES.toMillis(5),
TimeUnit.MINUTES.toMillis(15)
};
+
+ private static final LinkedHashSet<VcnUnderlyingNetworkPriority>
+ DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
+
+ static {
+ DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+ new VcnCellUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .setAllowRoaming(true /* allowRoaming */)
+ .setRequireOpportunistic(true /* requireOpportunistic */)
+ .build());
+
+ DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+ new VcnWifiUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .build());
+
+ DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+ new VcnCellUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .setAllowRoaming(true /* allowRoaming */)
+ .setRequireOpportunistic(false /* requireOpportunistic */)
+ .build());
+ }
+
private static final String GATEWAY_CONNECTION_NAME_KEY = "mGatewayConnectionName";
@NonNull private final String mGatewayConnectionName;
@@ -166,6 +196,9 @@
private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
@NonNull private final SortedSet<Integer> mExposedCapabilities;
+ private static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+ @NonNull private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities;
+
private static final String MAX_MTU_KEY = "mMaxMtu";
private final int mMaxMtu;
@@ -177,6 +210,7 @@
@NonNull String gatewayConnectionName,
@NonNull IkeTunnelConnectionParams tunnelConnectionParams,
@NonNull Set<Integer> exposedCapabilities,
+ @NonNull LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
@NonNull long[] retryIntervalsMs,
@IntRange(from = MIN_MTU_V6) int maxMtu) {
mGatewayConnectionName = gatewayConnectionName;
@@ -185,6 +219,11 @@
mRetryIntervalsMs = retryIntervalsMs;
mMaxMtu = maxMtu;
+ mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities);
+ if (mUnderlyingNetworkPriorities.isEmpty()) {
+ mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+ }
+
validate();
}
@@ -198,12 +237,19 @@
final PersistableBundle exposedCapsBundle =
in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
+ final PersistableBundle networkPrioritiesBundle =
+ in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
mGatewayConnectionName = in.getString(GATEWAY_CONNECTION_NAME_KEY);
mTunnelConnectionParams =
TunnelConnectionParamsUtils.fromPersistableBundle(tunnelConnectionParamsBundle);
mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
+ mUnderlyingNetworkPriorities =
+ new LinkedHashSet<>(
+ PersistableBundleUtils.toList(
+ networkPrioritiesBundle,
+ VcnUnderlyingNetworkPriority::fromPersistableBundle));
mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
mMaxMtu = in.getInt(MAX_MTU_KEY);
@@ -221,6 +267,7 @@
checkValidCapability(cap);
}
+ Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
validateRetryInterval(mRetryIntervalsMs);
@@ -303,6 +350,18 @@
}
/**
+ * Retrieve the configured VcnUnderlyingNetworkPriority list, or a default list if it is not
+ * configured.
+ *
+ * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkPriority>)
+ * @hide
+ */
+ @NonNull
+ public LinkedHashSet<VcnUnderlyingNetworkPriority> getVcnUnderlyingNetworkPriorities() {
+ return new LinkedHashSet<>(mUnderlyingNetworkPriorities);
+ }
+
+ /**
* Retrieves the configured retry intervals.
*
* @see Builder#setRetryIntervalsMillis(long[])
@@ -338,10 +397,15 @@
PersistableBundleUtils.fromList(
new ArrayList<>(mExposedCapabilities),
PersistableBundleUtils.INTEGER_SERIALIZER);
+ final PersistableBundle networkPrioritiesBundle =
+ PersistableBundleUtils.fromList(
+ new ArrayList<>(mUnderlyingNetworkPriorities),
+ VcnUnderlyingNetworkPriority::toPersistableBundle);
result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
+ result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle);
result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
result.putInt(MAX_MTU_KEY, mMaxMtu);
@@ -379,6 +443,11 @@
@NonNull private final String mGatewayConnectionName;
@NonNull private final IkeTunnelConnectionParams mTunnelConnectionParams;
@NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
+
+ @NonNull
+ private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities =
+ new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+
@NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
private int mMaxMtu = DEFAULT_MAX_MTU;
@@ -450,6 +519,33 @@
}
/**
+ * Set the VcnUnderlyingNetworkPriority list.
+ *
+ * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that
+ * are ordered from most to least preferred, or an empty list to use the default
+ * prioritization. The default network prioritization is Opportunistic cellular, Carrier
+ * WiFi and Macro cellular
+ * @return
+ */
+ /** @hide */
+ @NonNull
+ public Builder setVcnUnderlyingNetworkPriorities(
+ @NonNull LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities) {
+ Objects.requireNonNull(
+ mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+
+ mUnderlyingNetworkPriorities.clear();
+
+ if (underlyingNetworkPriorities.isEmpty()) {
+ mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+ } else {
+ mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities);
+ }
+
+ return this;
+ }
+
+ /**
* Set the retry interval between VCN establishment attempts upon successive failures.
*
* <p>The last retry interval will be repeated until safe mode is entered, or a connection
@@ -513,6 +609,7 @@
mGatewayConnectionName,
mTunnelConnectionParams,
mExposedCapabilities,
+ mUnderlyingNetworkPriorities,
mRetryIntervalsMs,
mMaxMtu);
}
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..82f6ae7
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+// TODO: Add documents
+/** @hide */
+public abstract class VcnUnderlyingNetworkPriority {
+ /** @hide */
+ protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
+ /** @hide */
+ protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
+
+ /** Denotes that network quality needs to be OK */
+ public static final int NETWORK_QUALITY_OK = 10000;
+ /** Denotes that any network quality is acceptable */
+ public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
+ public @interface NetworkQuality {}
+
+ private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType";
+ private final int mNetworkPriorityType;
+
+ /** @hide */
+ protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+ private final int mNetworkQuality;
+
+ /** @hide */
+ protected static final String ALLOW_METERED_KEY = "mAllowMetered";
+ private final boolean mAllowMetered;
+
+ /** @hide */
+ protected VcnUnderlyingNetworkPriority(
+ int networkPriorityType, int networkQuality, boolean allowMetered) {
+ mNetworkPriorityType = networkPriorityType;
+ mNetworkQuality = networkQuality;
+ mAllowMetered = allowMetered;
+ }
+
+ private static void validateNetworkQuality(int networkQuality) {
+ Preconditions.checkArgument(
+ networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
+ "Invalid networkQuality:" + networkQuality);
+ }
+
+ /** @hide */
+ protected void validate() {
+ validateNetworkQuality(mNetworkQuality);
+ }
+
+ /** @hide */
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ public static VcnUnderlyingNetworkPriority fromPersistableBundle(
+ @NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle is null");
+
+ final int networkPriorityType = in.getInt(NETWORK_PRIORITY_TYPE_KEY);
+ switch (networkPriorityType) {
+ case NETWORK_PRIORITY_TYPE_WIFI:
+ return VcnWifiUnderlyingNetworkPriority.fromPersistableBundle(in);
+ case NETWORK_PRIORITY_TYPE_CELL:
+ return VcnCellUnderlyingNetworkPriority.fromPersistableBundle(in);
+ default:
+ throw new IllegalArgumentException(
+ "Invalid networkPriorityType:" + networkPriorityType);
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
+ result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
+ result.putBoolean(ALLOW_METERED_KEY, mAllowMetered);
+
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof VcnUnderlyingNetworkPriority)) {
+ return false;
+ }
+
+ final VcnUnderlyingNetworkPriority rhs = (VcnUnderlyingNetworkPriority) other;
+ return mNetworkPriorityType == rhs.mNetworkPriorityType
+ && mNetworkQuality == rhs.mNetworkQuality
+ && mAllowMetered == rhs.mAllowMetered;
+ }
+
+ /** Retrieve the required network quality. */
+ @NetworkQuality
+ public int getNetworkQuality() {
+ return mNetworkQuality;
+ }
+
+ /** Return if a metered network is allowed. */
+ public boolean allowMetered() {
+ return mAllowMetered;
+ }
+
+ /**
+ * This class is used to incrementally build VcnUnderlyingNetworkPriority objects.
+ *
+ * @param <T> The subclass to be built.
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /** @hide */
+ protected int mNetworkQuality = NETWORK_QUALITY_ANY;
+ /** @hide */
+ protected boolean mAllowMetered = false;
+
+ /** @hide */
+ protected Builder() {}
+
+ /**
+ * Set the required network quality.
+ *
+ * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+ */
+ @NonNull
+ public T setNetworkQuality(@NetworkQuality int networkQuality) {
+ validateNetworkQuality(networkQuality);
+
+ mNetworkQuality = networkQuality;
+ return self();
+ }
+
+ /**
+ * Set if a metered network is allowed.
+ *
+ * @param allowMetered the flag to indicate if a metered network is allowed, defaults to
+ * {@code false}
+ */
+ @NonNull
+ public T setAllowMetered(boolean allowMetered) {
+ mAllowMetered = allowMetered;
+ return self();
+ }
+
+ /** @hide */
+ abstract T self();
+ }
+}
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..fc7e7e2
--- /dev/null
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+// TODO: Add documents
+/** @hide */
+public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority {
+ private static final String SSID_KEY = "mSsid";
+ @Nullable private final String mSsid;
+
+ private VcnWifiUnderlyingNetworkPriority(
+ int networkQuality, boolean allowMetered, String ssid) {
+ super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered);
+ mSsid = ssid;
+
+ validate();
+ }
+
+ /** @hide */
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ public static VcnWifiUnderlyingNetworkPriority fromPersistableBundle(
+ @NonNull PersistableBundle in) {
+ Objects.requireNonNull(in, "PersistableBundle is null");
+
+ final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
+ final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+ final String ssid = in.getString(SSID_KEY);
+ return new VcnWifiUnderlyingNetworkPriority(networkQuality, allowMetered, ssid);
+ }
+
+ /** @hide */
+ @Override
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PROTECTED)
+ public PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = super.toPersistableBundle();
+ result.putString(SSID_KEY, mSsid);
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSsid);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!super.equals(other)) {
+ return false;
+ }
+
+ if (!(other instanceof VcnWifiUnderlyingNetworkPriority)) {
+ return false;
+ }
+
+ final VcnWifiUnderlyingNetworkPriority rhs = (VcnWifiUnderlyingNetworkPriority) other;
+ return mSsid == rhs.mSsid;
+ }
+
+ /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
+ @Nullable
+ public String getSsid() {
+ return mSsid;
+ }
+
+ /** This class is used to incrementally build VcnWifiUnderlyingNetworkPriority objects. */
+ public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+ @Nullable private String mSsid;
+
+ /** Construct a Builder object. */
+ public Builder() {}
+
+ /**
+ * Set the required SSID.
+ *
+ * @param ssid the required SSID, or {@code null} if any SSID is acceptable.
+ */
+ @NonNull
+ public Builder setSsid(@Nullable String ssid) {
+ mSsid = ssid;
+ return this;
+ }
+
+ /** Build the VcnWifiUnderlyingNetworkPriority. */
+ @NonNull
+ public VcnWifiUnderlyingNetworkPriority build() {
+ return new VcnWifiUnderlyingNetworkPriority(mNetworkQuality, mAllowMetered, mSsid);
+ }
+
+ /** @hide */
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index afd0ff7..3664515 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3646,7 +3646,14 @@
* list was {@code null}, {@code list} is cleared.
*
* @see #writeParcelableList(List, int)
+ *
+ * @deprecated Use the type-safer version {@link #readParcelableList(List, ClassLoader, Class)}
+ * starting from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the
+ * format to use {@link #readTypedList(List, Parcelable.Creator)} if possible (eg. if the
+ * items' class is final) since this is also more performant. Note that changing to the
+ * latter also requires changing the writes.
*/
+ @Deprecated
@NonNull
public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list,
@Nullable ClassLoader cl) {
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 3e01c53..b7e3068 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -238,7 +238,7 @@
public static final int DISABLED = 9;
}
- private IUpdateEngine mUpdateEngine;
+ private final IUpdateEngine mUpdateEngine;
private IUpdateEngineCallback mUpdateEngineCallback = null;
private final Object mUpdateEngineCallbackLock = new Object();
@@ -248,6 +248,9 @@
public UpdateEngine() {
mUpdateEngine = IUpdateEngine.Stub.asInterface(
ServiceManager.getService(UPDATE_ENGINE_SERVICE));
+ if (mUpdateEngine == null) {
+ throw new IllegalStateException("Failed to find update_engine");
+ }
}
/**
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 9612ca6..5831573 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -136,7 +136,7 @@
/**
* @hide
*/
- @IntDef(prefix = { "FLAG_" }, value = {
+ @IntDef(prefix = { "FLAG_" }, flag = true, value = {
FLAG_BYPASS_INTERRUPTION_POLICY,
})
@Retention(RetentionPolicy.SOURCE)
@@ -162,7 +162,8 @@
private final int mFlags;
private final int mOriginalAudioUsage;
- private VibrationAttributes(int usage, int audioUsage, int flags) {
+ private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
+ @Flag int flags) {
mUsage = usage;
mOriginalAudioUsage = audioUsage;
mFlags = flags & FLAG_ALL_SUPPORTED;
@@ -172,6 +173,7 @@
* Return the vibration usage class.
* @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN
*/
+ @UsageClass
public int getUsageClass() {
return mUsage & USAGE_CLASS_MASK;
}
@@ -180,6 +182,7 @@
* Return the vibration usage.
* @return one of the values that can be set in {@link Builder#setUsage(int)}
*/
+ @Usage
public int getUsage() {
return mUsage;
}
@@ -188,6 +191,7 @@
* Return the flags.
* @return a combined mask of all flags
*/
+ @Flag
public int getFlags() {
return mFlags;
}
@@ -196,7 +200,7 @@
* Check whether a flag is set
* @return true if a flag is set and false otherwise
*/
- public boolean isFlagSet(int flag) {
+ public boolean isFlagSet(@Flag int flag) {
return (mFlags & flag) > 0;
}
@@ -206,6 +210,7 @@
* @hide
*/
@TestApi
+ @AudioAttributes.AttributeUsage
public int getAudioUsage() {
if (mOriginalAudioUsage != AudioAttributes.USAGE_UNKNOWN) {
// Return same audio usage set in the Builder.
@@ -292,7 +297,7 @@
}
/** @hide */
- public static String usageToString(int usage) {
+ public static String usageToString(@Usage int usage) {
switch (usage) {
case USAGE_UNKNOWN:
return "UNKNOWN";
@@ -419,7 +424,7 @@
* {@link VibrationAttributes#USAGE_MEDIA}.
* @return the same Builder instance.
*/
- public @NonNull Builder setUsage(int usage) {
+ public @NonNull Builder setUsage(@Usage int usage) {
mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
mUsage = usage;
return this;
@@ -431,7 +436,7 @@
* @param mask Bit range that should be changed.
* @return the same Builder instance.
*/
- public @NonNull Builder setFlags(int flags, int mask) {
+ public @NonNull Builder setFlags(@Flag int flags, int mask) {
mask &= FLAG_ALL_SUPPORTED;
mFlags = (mFlags & ~mask) | (flags & mask);
return this;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 2adcbc3..0c2f8b6 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -99,6 +99,7 @@
@UnsupportedAppUsage
private final boolean mRemovable;
private final boolean mEmulated;
+ private final boolean mStub;
private final boolean mAllowMassStorage;
private final long mMaxFileSize;
private final UserHandle mOwner;
@@ -137,8 +138,9 @@
/** {@hide} */
public StorageVolume(String id, File path, File internalPath, String description,
- boolean primary, boolean removable, boolean emulated, boolean allowMassStorage,
- long maxFileSize, UserHandle owner, UUID uuid, String fsUuid, String state) {
+ boolean primary, boolean removable, boolean emulated, boolean stub,
+ boolean allowMassStorage, long maxFileSize, UserHandle owner, UUID uuid, String fsUuid,
+ String state) {
mId = Preconditions.checkNotNull(id);
mPath = Preconditions.checkNotNull(path);
mInternalPath = Preconditions.checkNotNull(internalPath);
@@ -146,6 +148,7 @@
mPrimary = primary;
mRemovable = removable;
mEmulated = emulated;
+ mStub = stub;
mAllowMassStorage = allowMassStorage;
mMaxFileSize = maxFileSize;
mOwner = Preconditions.checkNotNull(owner);
@@ -162,6 +165,7 @@
mPrimary = in.readInt() != 0;
mRemovable = in.readInt() != 0;
mEmulated = in.readInt() != 0;
+ mStub = in.readInt() != 0;
mAllowMassStorage = in.readInt() != 0;
mMaxFileSize = in.readLong();
mOwner = in.readParcelable(null);
@@ -264,13 +268,23 @@
/**
* Returns true if the volume is emulated.
*
- * @return is removable
+ * @return is emulated
*/
public boolean isEmulated() {
return mEmulated;
}
/**
+ * Returns true if the volume is a stub volume (a volume managed from outside Android).
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isStub() {
+ return mStub;
+ }
+
+ /**
* Returns true if this volume can be shared via USB mass storage.
*
* @return whether mass storage is allowed
@@ -506,6 +520,7 @@
pw.printPair("mPrimary", mPrimary);
pw.printPair("mRemovable", mRemovable);
pw.printPair("mEmulated", mEmulated);
+ pw.printPair("mStub", mStub);
pw.printPair("mAllowMassStorage", mAllowMassStorage);
pw.printPair("mMaxFileSize", mMaxFileSize);
pw.printPair("mOwner", mOwner);
@@ -540,6 +555,7 @@
parcel.writeInt(mPrimary ? 1 : 0);
parcel.writeInt(mRemovable ? 1 : 0);
parcel.writeInt(mEmulated ? 1 : 0);
+ parcel.writeInt(mStub ? 1 : 0);
parcel.writeInt(mAllowMassStorage ? 1 : 0);
parcel.writeLong(mMaxFileSize);
parcel.writeParcelable(mOwner, flags);
@@ -621,6 +637,7 @@
mPrimary,
mRemovable,
mEmulated,
+ /* stub= */ false,
/* allowMassStorage= */ false,
/* maxFileSize= */ 0,
mOwner,
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 39a2e13..ebd143c 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -404,6 +404,7 @@
final boolean removable;
final boolean emulated;
+ final boolean stub = type == TYPE_STUB;
final boolean allowMassStorage = false;
final String envState = reportUnmounted
? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
@@ -459,8 +460,8 @@
}
return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable,
- emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
- uuid, derivedFsUuid, envState);
+ emulated, stub, allowMassStorage, maxFileSize, new UserHandle(userId), uuid,
+ derivedFsUuid, envState);
}
@UnsupportedAppUsage
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1cc982e7..eac09aa 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -105,6 +105,7 @@
final boolean primary = false;
final boolean removable = true;
final boolean emulated = false;
+ final boolean stub = false;
final boolean allowMassStorage = false;
final long maxFileSize = 0;
final UserHandle user = new UserHandle(UserHandle.USER_NULL);
@@ -116,7 +117,8 @@
}
return new StorageVolume(id, userPath, internalPath, description, primary, removable,
- emulated, allowMassStorage, maxFileSize, user, null /* uuid */, fsUuid, envState);
+ emulated, stub, allowMassStorage, maxFileSize, user, null /* uuid */, fsUuid,
+ envState);
}
public void dump(IndentingPrintWriter pw) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e267db4..7bb290d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11427,22 +11427,38 @@
"night_display_forced_auto_mode_available";
/**
- * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
- * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
- * exceeded.
- * @hide
- */
+ * If UTC time between two NITZ signals is greater than this value then the second signal
+ * cannot be ignored.
+ *
+ * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+ * detection.
+ * @hide
+ */
@Readable
public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
/**
- * The length of time in milli-seconds that automatic small adjustments to
- * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
- * @hide
- */
+ * If the elapsed realtime between two NITZ signals is greater than this value then the
+ * second signal cannot be ignored.
+ *
+ * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+ * detection.
+ * @hide
+ */
@Readable
public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
+ /**
+ * If the device connects to a telephony network and was disconnected from a telephony
+ * network for less than this time, a previously received NITZ signal can be restored.
+ *
+ * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+ * detection.
+ * @hide
+ */
+ public static final String NITZ_NETWORK_DISCONNECT_RETENTION =
+ "nitz_network_disconnect_retention";
+
/** Preferred NTP server. {@hide} */
@Readable
public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 5d84af0..50a44a1 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3775,6 +3775,25 @@
public static final String NETWORK_TYPE_BITMASK = "network_type_bitmask";
/**
+ * Lingering radio technology (network type) bitmask.
+ * To check what values can be contained, refer to the NETWORK_TYPE_ constants in
+ * {@link android.telephony.TelephonyManager}.
+ * Bitmask for a radio tech R is (1 << (R - 1))
+ * <P>Type: INTEGER (long)</P>
+ * @hide
+ */
+ public static final String LINGERING_NETWORK_TYPE_BITMASK =
+ "lingering_network_type_bitmask";
+
+ /**
+ * Sets whether the PDU session brought up by this APN should always be on.
+ * See 3GPP TS 23.501 section 5.6.13
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String ALWAYS_ON = "always_on";
+
+ /**
* MVNO type:
* {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
* <P>Type: TEXT</P>
@@ -3852,11 +3871,31 @@
* connected, in bytes.
* <p>Type: INTEGER </p>
* @hide
+ * @deprecated use {@link #MTU_V4} or {@link #MTU_V6} instead
*/
@SystemApi
+ @Deprecated
public static final String MTU = "mtu";
/**
+ * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is
+ * connected, in bytes.
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ @SystemApi
+ public static final String MTU_V4 = "mtu_v4";
+
+ /**
+ * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is
+ * connected, in bytes.
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ @SystemApi
+ public static final String MTU_V6 = "mtu_v6";
+
+ /**
* APN edit status. APN could be added/edited/deleted by a user or carrier.
* see all possible returned APN edit status.
* <ul>
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a45a91e..414a7f1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3681,14 +3681,14 @@
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
- invalidateTopGlow();
+ invalidateEdgeEffects();
} else if (incrementalDeltaY < 0) {
mEdgeGlowBottom.onPullDistance((float) overscroll / getHeight(),
1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
- invalidateBottomGlow();
+ invalidateEdgeEffects();
}
}
}
@@ -3728,7 +3728,7 @@
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
- invalidateTopGlow();
+ invalidateEdgeEffects();
} else if (rawDeltaY < 0) {
mEdgeGlowBottom.onPullDistance(
(float) -overScrollDistance / getHeight(),
@@ -3736,7 +3736,7 @@
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
- invalidateBottomGlow();
+ invalidateEdgeEffects();
}
}
}
@@ -3782,17 +3782,21 @@
// First allow releasing existing overscroll effect:
float consumed = 0;
if (mEdgeGlowTop.getDistance() != 0) {
- consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
- (float) x / getWidth());
- if (consumed != 0f) {
- invalidateTopGlow();
+ if (canScrollUp()) {
+ mEdgeGlowTop.onRelease();
+ } else {
+ consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
+ (float) x / getWidth());
}
+ invalidateEdgeEffects();
} else if (mEdgeGlowBottom.getDistance() != 0) {
- consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
- 1f - (float) x / getWidth());
- if (consumed != 0f) {
- invalidateBottomGlow();
+ if (canScrollDown()) {
+ mEdgeGlowBottom.onRelease();
+ } else {
+ consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
+ 1f - (float) x / getWidth());
}
+ invalidateEdgeEffects();
}
int pixelsConsumed = Math.round(consumed * getHeight());
return deltaY - pixelsConsumed;
@@ -3802,30 +3806,16 @@
* @return <code>true</code> if either the top or bottom edge glow is currently active or
* <code>false</code> if it has no value to release.
*/
- private boolean isGlowActive() {
- return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0;
+ private boolean doesTouchStopStretch() {
+ return (mEdgeGlowBottom.getDistance() != 0 && !canScrollDown())
+ || (mEdgeGlowTop.getDistance() != 0 && !canScrollUp());
}
- private void invalidateTopGlow() {
+ private void invalidateEdgeEffects() {
if (!shouldDisplayEdgeEffects()) {
return;
}
- final boolean clipToPadding = getClipToPadding();
- final int top = clipToPadding ? mPaddingTop : 0;
- final int left = clipToPadding ? mPaddingLeft : 0;
- final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
- invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
- }
-
- private void invalidateBottomGlow() {
- if (!shouldDisplayEdgeEffects()) {
- return;
- }
- final boolean clipToPadding = getClipToPadding();
- final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
- final int left = clipToPadding ? mPaddingLeft : 0;
- final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
- invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
+ invalidate();
}
@Override
@@ -4468,7 +4458,7 @@
final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
canvas.translate(translateX, edgeY);
if (mEdgeGlowTop.draw(canvas)) {
- invalidateTopGlow();
+ invalidateEdgeEffects();
}
canvas.restoreToCount(restoreCount);
}
@@ -4482,7 +4472,7 @@
canvas.translate(edgeX, edgeY);
canvas.rotate(180, width, 0);
if (mEdgeGlowBottom.draw(canvas)) {
- invalidateBottomGlow();
+ invalidateEdgeEffects();
}
canvas.restoreToCount(restoreCount);
}
@@ -4572,7 +4562,7 @@
mActivePointerId = ev.getPointerId(0);
int motionPosition = findMotionRow(y);
- if (isGlowActive()) {
+ if (doesTouchStopStretch()) {
// Pressed during edge effect, so this is considered the same as a fling catch.
touchMode = mTouchMode = TOUCH_MODE_FLING;
} else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
@@ -6579,7 +6569,7 @@
*/
public void setBottomEdgeEffectColor(@ColorInt int color) {
mEdgeGlowBottom.setColor(color);
- invalidateBottomGlow();
+ invalidateEdgeEffects();
}
/**
@@ -6593,7 +6583,7 @@
*/
public void setTopEdgeEffectColor(@ColorInt int color) {
mEdgeGlowTop.setColor(color);
- invalidateTopGlow();
+ invalidateEdgeEffects();
}
/**
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 7a960c6..5e75797 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -368,10 +368,12 @@
*/
@NonNull
public WindowContainerTransaction setAdjacentRoots(
- @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
+ @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2,
+ boolean moveTogether) {
mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
root1.asBinder(),
- root2.asBinder()));
+ root2.asBinder(),
+ moveTogether));
return this;
}
@@ -975,6 +977,9 @@
private boolean mReparentTopOnly;
+ // TODO(b/207185041): Remove this once having a single-top root for split screen.
+ private boolean mMoveAdjacentTogether;
+
@Nullable
private int[] mWindowingModes;
@@ -1033,10 +1038,13 @@
.build();
}
- public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) {
+ /** Create a hierarchy op for setting adjacent root tasks. */
+ public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2,
+ boolean moveTogether) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
.setContainer(root1)
.setReparentContainer(root2)
+ .setMoveAdjacentTogether(moveTogether)
.build();
}
@@ -1070,6 +1078,7 @@
mReparent = copy.mReparent;
mToTop = copy.mToTop;
mReparentTopOnly = copy.mReparentTopOnly;
+ mMoveAdjacentTogether = copy.mMoveAdjacentTogether;
mWindowingModes = copy.mWindowingModes;
mActivityTypes = copy.mActivityTypes;
mLaunchOptions = copy.mLaunchOptions;
@@ -1084,6 +1093,7 @@
mReparent = in.readStrongBinder();
mToTop = in.readBoolean();
mReparentTopOnly = in.readBoolean();
+ mMoveAdjacentTogether = in.readBoolean();
mWindowingModes = in.createIntArray();
mActivityTypes = in.createIntArray();
mLaunchOptions = in.readBundle();
@@ -1128,6 +1138,10 @@
return mReparentTopOnly;
}
+ public boolean getMoveAdjacentTogether() {
+ return mMoveAdjacentTogether;
+ }
+
public int[] getWindowingModes() {
return mWindowingModes;
}
@@ -1175,7 +1189,8 @@
return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
return "{SetAdjacentRoot: container=" + mContainer
- + " adjacentRoot=" + mReparent + "}";
+ + " adjacentRoot=" + mReparent + " mMoveAdjacentTogether="
+ + mMoveAdjacentTogether + "}";
case HIERARCHY_OP_TYPE_LAUNCH_TASK:
return "{LaunchTask: " + mLaunchOptions + "}";
case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
@@ -1212,6 +1227,7 @@
dest.writeStrongBinder(mReparent);
dest.writeBoolean(mToTop);
dest.writeBoolean(mReparentTopOnly);
+ dest.writeBoolean(mMoveAdjacentTogether);
dest.writeIntArray(mWindowingModes);
dest.writeIntArray(mActivityTypes);
dest.writeBundle(mLaunchOptions);
@@ -1251,6 +1267,8 @@
private boolean mReparentTopOnly;
+ private boolean mMoveAdjacentTogether;
+
@Nullable
private int[] mWindowingModes;
@@ -1293,6 +1311,11 @@
return this;
}
+ Builder setMoveAdjacentTogether(boolean moveAdjacentTogether) {
+ mMoveAdjacentTogether = moveAdjacentTogether;
+ return this;
+ }
+
Builder setWindowingModes(@Nullable int[] windowingModes) {
mWindowingModes = windowingModes;
return this;
@@ -1336,6 +1359,7 @@
: null;
hierarchyOp.mToTop = mToTop;
hierarchyOp.mReparentTopOnly = mReparentTopOnly;
+ hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether;
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 0723766..4dc9aa5 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -328,7 +328,7 @@
if (multiStateCounter != null) {
if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
mAccumulatedMultiStateChargeMicroCoulomb =
- new LongMultiStateCounter[numWrittenEntries];
+ new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
}
mAccumulatedMultiStateChargeMicroCoulomb[index] = multiStateCounter;
}
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 2f12289..0f647ea 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -346,6 +346,11 @@
return surfaceTexture->getTimestamp();
}
+static jlong SurfaceTexture_getDataSpace(JNIEnv* env, jobject thiz) {
+ sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
+ return surfaceTexture->getCurrentDataSpace();
+}
+
static void SurfaceTexture_release(JNIEnv* env, jobject thiz)
{
sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
@@ -361,17 +366,18 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceTextureMethods[] = {
- {"nativeInit", "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
- {"nativeFinalize", "()V", (void*)SurfaceTexture_finalize },
- {"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
- {"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage },
- {"nativeReleaseTexImage", "()V", (void*)SurfaceTexture_releaseTexImage },
- {"nativeDetachFromGLContext", "()I", (void*)SurfaceTexture_detachFromGLContext },
- {"nativeAttachToGLContext", "(I)I", (void*)SurfaceTexture_attachToGLContext },
- {"nativeGetTransformMatrix", "([F)V", (void*)SurfaceTexture_getTransformMatrix },
- {"nativeGetTimestamp", "()J", (void*)SurfaceTexture_getTimestamp },
- {"nativeRelease", "()V", (void*)SurfaceTexture_release },
- {"nativeIsReleased", "()Z", (void*)SurfaceTexture_isReleased },
+ {"nativeInit", "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init},
+ {"nativeFinalize", "()V", (void*)SurfaceTexture_finalize},
+ {"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize},
+ {"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage},
+ {"nativeReleaseTexImage", "()V", (void*)SurfaceTexture_releaseTexImage},
+ {"nativeDetachFromGLContext", "()I", (void*)SurfaceTexture_detachFromGLContext},
+ {"nativeAttachToGLContext", "(I)I", (void*)SurfaceTexture_attachToGLContext},
+ {"nativeGetTransformMatrix", "([F)V", (void*)SurfaceTexture_getTransformMatrix},
+ {"nativeGetTimestamp", "()J", (void*)SurfaceTexture_getTimestamp},
+ {"nativeGetDataSpace", "()J", (void*)SurfaceTexture_getDataSpace},
+ {"nativeRelease", "()V", (void*)SurfaceTexture_release},
+ {"nativeIsReleased", "()Z", (void*)SurfaceTexture_isReleased},
};
int register_android_graphics_SurfaceTexture(JNIEnv* env)
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 268871b..8b45907 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2284,10 +2284,8 @@
return jStatus;
}
-static jint
-android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP(
- JNIEnv *env, jobject thiz, jobject jEncodingFormatList)
-{
+static jint android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia(
+ JNIEnv *env, jobject thiz, jint deviceType, jobject jEncodingFormatList) {
ALOGV("%s", __FUNCTION__);
jint jStatus = AUDIO_JAVA_SUCCESS;
if (!env->IsInstanceOf(jEncodingFormatList, gArrayListClass)) {
@@ -2295,8 +2293,10 @@
return (jint)AUDIO_JAVA_BAD_VALUE;
}
std::vector<audio_format_t> encodingFormats;
- status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP(
- &encodingFormats);
+ status_t status =
+ AudioSystem::getHwOffloadFormatsSupportedForBluetoothMedia(static_cast<audio_devices_t>(
+ deviceType),
+ &encodingFormats);
if (status != NO_ERROR) {
ALOGE("%s: error %d", __FUNCTION__, status);
jStatus = nativeToJavaStatus(status);
@@ -2875,8 +2875,8 @@
{"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
{"isHapticPlaybackSupported", "()Z",
(void *)android_media_AudioSystem_isHapticPlaybackSupported},
- {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I",
- (void *)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP},
+ {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
+ (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia},
{"setSupportedSystemUsages", "([I)I",
(void *)android_media_AudioSystem_setSupportedSystemUsages},
{"setAllowedCapturePolicy", "(II)I",
@@ -2919,7 +2919,6 @@
"[Landroid/media/AudioDeviceAttributes;)Z",
(void *)android_media_AudioSystem_canBeSpatialized}};
-
static const JNINativeMethod gEventHandlerMethods[] = {
{"native_setup",
"(Ljava/lang/Object;)V",
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b33ca1d..b52aa82 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -32,16 +32,8 @@
}
/*
- * An obfuscated and simplified time zone suggestion for metrics use.
- *
- * The suggestion's time zone IDs (which relate to location) are obfuscated by
- * mapping them to an ordinal. When the ordinal is assigned consistently across
- * several objects (i.e. so the same time zone ID is always mapped to the same
- * ordinal), this allows comparisons between those objects. For example, we can
- * answer "did these two suggestions agree?", "does the suggestion match the
- * device's current time zone?", without leaking knowledge of location. Ordinals
- * are also significantly more compact than full IANA TZDB IDs, albeit highly
- * unstable and of limited use.
+ * A generic-form time zone suggestion for metrics use. Required to be a superset of the
+ * MetricsTimeZoneSuggestion proto defined in atoms.proto to ensure binary compatibility.
*/
message MetricsTimeZoneSuggestion {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -55,5 +47,24 @@
// The ordinals for time zone(s) in the suggestion. Always empty for
// UNCERTAIN, and can be empty for CERTAIN, for example when the device is in
// a disputed area / on an ocean.
- repeated uint32 time_zone_ordinals = 2;
+ //
+ // The suggestion's time zone IDs (which relate to location) are obfuscated by
+ // mapping them to an ordinal. When the ordinal is assigned consistently across
+ // several objects (i.e. so the same time zone ID is always mapped to the same
+ // ordinal), this allows comparisons between those objects. For example, we can
+ // answer "did these two suggestions agree?", "does the suggestion match the
+ // device's current time zone?", without leaking knowledge of location. Ordinals
+ // are also significantly more compact than full IANA TZDB IDs, albeit unstable
+ // and of limited use.
+ repeated int32 time_zone_ordinals = 2;
+
+ // The actual time zone ID(s) in the suggestion. Similar to time_zone_ordinals
+ // but contains the actual string IDs.
+ //
+ // This information is only captured / reported for some devices based on the
+ // value of a server side flag, i.e. it could be enabled for internal testers.
+ // Therefore the list can be empty even when time_zone_ordinals is populated.
+ //
+ // When enabled, see time_zone_ordinals for the expected number of values.
+ repeated string time_zone_ids = 3;
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index db5d49d..b406578 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -672,18 +672,31 @@
optional SettingProto new_contact_aggregator = 79 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto night_display_forced_auto_mode_available = 80 [ (android.privacy).dest = DEST_AUTOMATIC ];
- message NitzUpdate {
+ message Nitz {
option (android.msg_privacy).dest = DEST_EXPLICIT;
- // If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment to
- // SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
- // exceeded.
- optional SettingProto diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
- // The length of time in milli-seconds that automatic small adjustments to
- // SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
- optional SettingProto spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // If UTC time between two NITZ signals is greater than this value then the second signal
+ // cannot be ignored.
+ //
+ // This value is in milliseconds. It is used for telephony-based time and time zone
+ // detection.
+ optional SettingProto update_diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ // If the elapsed realtime between two NITZ signals is greater than this value then the
+ // second signal cannot be ignored.
+ //
+ // This value is in milliseconds. It is used for telephony-based time and time zone
+ // detection.
+ optional SettingProto update_spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ // If the device connects to a telephony network and was disconnected from a telephony
+ // network for less than this time, a previously received NITZ signal can be restored.
+ //
+ // This value is in milliseconds. It is used for telephony-based time and time zone
+ // detection.
+ optional SettingProto network_disconnect_retention = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
- optional NitzUpdate nitz_update = 81;
+ optional Nitz nitz = 81;
message Notification {
option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aada7eb..316ea34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4880,6 +4880,11 @@
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- @hide @SystemApi Allows an application to change the estimated launch time of an app.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE"
+ android:protectionLevel="signature|privileged" />
+
<!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
access the network and acquire wakelocks.
<p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f6a0e61..688bced 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3505,20 +3505,35 @@
<!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
is pressed during fingerprint enrollment. -->
- <string name="fp_enrollment_powerbutton_intent_title">Turn off screen?</string>
+ <string name="fp_power_button_enrollment_title">Continue setup?</string>
<!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
button is pressed during fingerprint enrollment. -->
- <string name="fp_enrollment_powerbutton_intent_message">While setting up your fingerprint, you
- pressed the Power button.\n\nThis usually turns off your screen.</string>
+ <string name="fp_power_button_enrollment_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint.</string>
<!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
power button is pressed during fingerprint enrollment. -->
- <string name="fp_enrollment_powerbutton_intent_positive_button">Turn off</string>
+ <string name="fp_power_button_enrollment_positive_button">Turn off screen</string>
<!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
power button is pressed during fingerprint enrollment. -->
- <string name="fp_enrollment_powerbutton_intent_negative_button">Cancel</string>
+ <string name="fp_power_button_enrollment_negative_button">Continue setup</string>
+
+ <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
+ is pressed during biometric prompt when a side fingerprint sensor is present. -->
+ <string name="fp_power_button_bp_title">Continue verifying your fingerprint?</string>
+
+ <!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
+ button is pressed during biometric prompt when a side fingerprint sensor is present. -->
+ <string name="fp_power_button_bp_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint.</string>
+
+ <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
+ power button is pressed during biometric prompt when a side fingerprint sensor is present. -->
+ <string name="fp_power_button_bp_positive_button">Turn off screen</string>
+
+ <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
+ power button is pressed during biometric prompt when a side fingerprint sensor is present. -->
+ <string name="fp_power_button_bp_negative_button">Continue</string>
<!-- Notification text to tell the user that a heavy-weight application is running. -->
<string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0495122..76e9774 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1840,10 +1840,14 @@
<java-symbol type="string" name="bugreport_status" />
<java-symbol type="string" name="bugreport_title" />
<java-symbol type="string" name="faceunlock_multiple_failures" />
- <java-symbol type="string" name="fp_enrollment_powerbutton_intent_title" />
- <java-symbol type="string" name="fp_enrollment_powerbutton_intent_message" />
- <java-symbol type="string" name="fp_enrollment_powerbutton_intent_positive_button" />
- <java-symbol type="string" name="fp_enrollment_powerbutton_intent_negative_button" />
+ <java-symbol type="string" name="fp_power_button_bp_title" />
+ <java-symbol type="string" name="fp_power_button_bp_message" />
+ <java-symbol type="string" name="fp_power_button_bp_positive_button" />
+ <java-symbol type="string" name="fp_power_button_bp_negative_button" />
+ <java-symbol type="string" name="fp_power_button_enrollment_title" />
+ <java-symbol type="string" name="fp_power_button_enrollment_message" />
+ <java-symbol type="string" name="fp_power_button_enrollment_positive_button" />
+ <java-symbol type="string" name="fp_power_button_enrollment_negative_button" />
<java-symbol type="string" name="global_actions" />
<java-symbol type="string" name="global_action_power_off" />
<java-symbol type="string" name="global_action_power_options" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
new file mode 100644
index 0000000..6471492
--- /dev/null
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test cases for {@link BluetoothLeAudioCodecConfig}.
+ */
+public class BluetoothLeAudioCodecConfigTest extends TestCase {
+ private int[] mCodecTypeArray = new int[] {
+ BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3,
+ BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID,
+ };
+
+ @SmallTest
+ public void testBluetoothLeAudioCodecConfig_valid_get_methods() {
+
+ for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) {
+ int codecType = mCodecTypeArray[codecIdx];
+
+ BluetoothLeAudioCodecConfig leAudioCodecConfig =
+ buildBluetoothLeAudioCodecConfig(codecType);
+
+ if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) {
+ assertEquals("LC3", leAudioCodecConfig.getCodecName());
+ }
+ if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName());
+ }
+
+ assertEquals(1, leAudioCodecConfig.getMaxCodecType());
+ assertEquals(codecType, leAudioCodecConfig.getCodecType());
+ }
+ }
+
+ private BluetoothLeAudioCodecConfig buildBluetoothLeAudioCodecConfig(int sourceCodecType) {
+ return new BluetoothLeAudioCodecConfig.Builder()
+ .setCodecType(sourceCodecType)
+ .build();
+
+ }
+}
diff --git a/core/tests/coretests/res/drawable/custom_drawable.xml b/core/tests/coretests/res/drawable/custom_drawable.xml
new file mode 100644
index 0000000..ebb821f
--- /dev/null
+++ b/core/tests/coretests/res/drawable/custom_drawable.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<drawable xmlns:android="http://schemas.android.com/apk/res/android"
+ class="android.window.CustomDrawable"
+ android:drawable="@drawable/bitmap_drawable"
+ android:inset="10dp" />
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/window/CustomDrawable.java b/core/tests/coretests/src/android/window/CustomDrawable.java
new file mode 100644
index 0000000..c25f877
--- /dev/null
+++ b/core/tests/coretests/src/android/window/CustomDrawable.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.graphics.drawable.InsetDrawable;
+
+public class CustomDrawable extends InsetDrawable {
+ public CustomDrawable() {
+ super(null, 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 83280f1..656e756 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
@@ -47,6 +48,8 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.frameworks.coretests.R;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -242,6 +245,12 @@
mInstrumentation.runOnMainSync(() -> wm.addView(subWindow, subWindowAttrs));
}
+ @Test
+ public void testGetCustomDrawable() {
+ assertNotNull(mWindowContext.getResources().getDrawable(R.drawable.custom_drawable,
+ null /* theme */));
+ }
+
private WindowContext createWindowContext() {
return createWindowContext(TYPE_APPLICATION_OVERLAY);
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cfcbc7d..f8db0606 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -269,7 +269,7 @@
onView(withId(R.id.content_preview_thumbnail)).check(matches(isDisplayed()));
}
- @Test @Ignore
+ @Test
public void twoOptionsAndUserSelectsOne() throws InterruptedException {
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -298,7 +298,7 @@
assertThat(chosen[0], is(toChoose));
}
- @Test @Ignore
+ @Test
public void fourOptionsStackedIntoOneTarget() throws InterruptedException {
Intent sendIntent = createSendTextIntent();
@@ -351,7 +351,7 @@
}
}
- @Test @Ignore
+ @Test
public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -461,7 +461,7 @@
assertThat(chosen[0], is(toChoose));
}
- @Test @Ignore
+ @Test
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -500,7 +500,7 @@
assertThat(chosen[0], is(toChoose));
}
- @Test @Ignore
+ @Test
public void hasLastChosenActivityAndOtherProfile() throws Exception {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -1549,7 +1549,7 @@
assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
}
- @Test @Ignore
+ @Test
public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -1947,7 +1947,7 @@
.SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
}
- @Test @Ignore
+ @Test
public void testSwitchProfileLogging() throws InterruptedException {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f83f401..0a2670a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -512,6 +512,8 @@
<permission name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE" />
<!-- Permission required for GTS test - GtsAssistIntentTestCases -->
<permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
+ <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
+ <permission name="android.permission.LOCK_DEVICE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 9b2effc..d84a24d 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -17,7 +17,9 @@
package android.graphics;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
+import android.hardware.DataSpace.NamedDataSpace;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -348,6 +350,14 @@
}
/**
+ * Retrieve the dataspace associated with the texture image.
+ */
+ @SuppressLint("MethodNameUnits")
+ public @NamedDataSpace long getDataSpace() {
+ return nativeGetDataSpace();
+ }
+
+ /**
* {@code release()} frees all the buffers and puts the SurfaceTexture into the
* 'abandoned' state. Once put in this state the SurfaceTexture can never
* leave it. When in the 'abandoned' state, all methods of the
@@ -416,6 +426,7 @@
private native void nativeFinalize();
private native void nativeGetTransformMatrix(float[] mtx);
private native long nativeGetTimestamp();
+ private native long nativeGetDataSpace();
private native void nativeSetDefaultBufferSize(int width, int height);
private native void nativeUpdateTexImage();
private native void nativeReleaseTexImage();
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
index 850c551..6fa1a69 100644
--- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
@@ -89,7 +89,7 @@
// specific sensor (the one that hasn't changed), and 2) currently the only
// signal to developers is the UserNotAuthenticatedException, which doesn't
// indicate a specific sensor.
- boolean canUnlockViaBiometrics = true;
+ boolean canUnlockViaBiometrics = biometricSids.length > 0;
for (long sid : biometricSids) {
if (!keySids.contains(sid)) {
canUnlockViaBiometrics = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 0c3a6b2..5161092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -95,9 +95,8 @@
// Update bitmap
val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
- bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
- ColorDrawable(colorAccent), fg),
- null /* user */, true /* shrinkNonAdaptiveIcons */).icon
+ bitmap = iconFactory.createBadgedIconBitmap(
+ AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
// Update dot path
dotPath = PathParser.createPathFromPathData(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 932f879..91aff3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -189,9 +189,7 @@
BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
b.isImportantConversation());
info.badgeBitmap = badgeBitmapInfo.icon;
- info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
- null /* user */,
- true /* shrinkNonAdaptiveIcons */).icon;
+ info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable).icon;
// Dot color & placement
Path iconPath = PathParser.createPathFromPathData(
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 0b3b25a..e30e6c5 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
@@ -794,7 +794,8 @@
if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ true /* moveTogether */);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
index 8d7fbce..a17942f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -634,7 +634,8 @@
mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ true /* moveTogether */);
// Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
// split to prevent new split behavior confusing users.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index a163f37..413627d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -390,8 +390,7 @@
final ShapeIconFactory factory = new ShapeIconFactory(
SplashscreenContentDrawer.this.mContext,
scaledIconDpi, mFinalIconSize);
- final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(
- iconDrawable, true /* shrinkNonAdaptiveIcons */);
+ final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
createIconDrawable(new BitmapDrawable(bitmap), true);
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index bbd4c81..35b6170 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -384,7 +384,16 @@
return base::unexpected(IOError::PAGES_MISSING);
}
- auto offset = dtohl(entry_offset_ptr.value());
+ uint32_t offset;
+ uint16_t res_idx;
+ if (type->flags & ResTable_type::FLAG_SPARSE) {
+ auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>();
+ offset = dtohs(sparse_entry->offset) * 4u;
+ res_idx = dtohs(sparse_entry->idx);
+ } else {
+ offset = dtohl(entry_offset_ptr.value());
+ res_idx = entry_idx;
+ }
if (offset != ResTable_type::NO_ENTRY) {
auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>();
if (!entry) {
@@ -394,7 +403,7 @@
if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
- return make_resid(0x00, *type_idx + type_id_offset_ + 1, entry_idx);
+ return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
}
}
}
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index f356c8130..d214e2d 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -95,6 +95,38 @@
ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
}
+TEST(LoadedArscTest, FindSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
+ &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
+ const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ // Ensure that AAPT2 sparsely encoded the v26 config as expected.
+ auto type_entry = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
+ ASSERT_NE(type_entry, type_spec->type_entries.end());
+ ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Test fetching a resource with only sparsely encoded configs by name.
+ auto id = package->FindEntryByName(u"string", u"only_v26");
+ ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
+}
+
TEST(LoadedArscTest, LoadSharedLibrary) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 243e74f..2492dbf 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -27,21 +27,22 @@
struct integer {
enum : uint32_t {
foo_0 = 0x7f010000,
- foo_1 = 0x7f010000,
- foo_2 = 0x7f010000,
- foo_3 = 0x7f010000,
- foo_4 = 0x7f010000,
- foo_5 = 0x7f010000,
- foo_6 = 0x7f010000,
- foo_7 = 0x7f010000,
- foo_8 = 0x7f010000,
- foo_9 = 0x7f010000,
+ foo_1 = 0x7f010001,
+ foo_2 = 0x7f010002,
+ foo_3 = 0x7f010003,
+ foo_4 = 0x7f010004,
+ foo_5 = 0x7f010005,
+ foo_6 = 0x7f010006,
+ foo_7 = 0x7f010007,
+ foo_8 = 0x7f010008,
+ foo_9 = 0x7f010009,
};
};
struct string {
enum : uint32_t {
foo_999 = 0x7f0203e7,
+ only_v26 = 0x7f0203e8
};
};
};
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index e7e1d60..4ea5468 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -14,5 +14,7 @@
fi
done
echo "</resources>" >> $OUTPUT_default
+
+echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
echo "</resources>" >> $OUTPUT_v26
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index 599a370..b08a621 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
index b6f8299..d116087e 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
@@ -333,4 +333,5 @@
<string name="foo_993">9930</string>
<string name="foo_996">9960</string>
<string name="foo_999">9990</string>
+ <string name="only_v26">only v26</string>
</resources>
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 1f9bba3..9fd01fb 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 337b45c..faae9a5 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -33,6 +33,7 @@
import android.app.compat.CompatChanges;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -6768,30 +6769,56 @@
/**
* Returns a list of audio formats that corresponds to encoding formats
- * supported on offload path for A2DP playback.
+ * supported on offload path for A2DP and LE audio playback.
*
+ * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType}
* @return a list of {@link BluetoothCodecConfig} objects containing encoding formats
- * supported for offload A2DP playback
+ * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig}
+ * objects containing encoding formats supported for offload LE Audio playback
* @hide
*/
- public List<BluetoothCodecConfig> getHwOffloadEncodingFormatsSupportedForA2DP() {
+ public List<?> getHwOffloadFormatsSupportedForBluetoothMedia(
+ @AudioSystem.DeviceType int deviceType) {
ArrayList<Integer> formatsList = new ArrayList<Integer>();
- ArrayList<BluetoothCodecConfig> codecConfigList = new ArrayList<BluetoothCodecConfig>();
+ ArrayList<BluetoothCodecConfig> a2dpCodecConfigList = new ArrayList<BluetoothCodecConfig>();
+ ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList =
+ new ArrayList<BluetoothLeAudioCodecConfig>();
- int status = AudioSystem.getHwOffloadEncodingFormatsSupportedForA2DP(formatsList);
+ if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
+ && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) {
+ throw new IllegalArgumentException(
+ "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia");
+ }
+
+ int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType,
+ formatsList);
if (status != AudioManager.SUCCESS) {
- Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status);
- return codecConfigList;
+ Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType "
+ + deviceType + " failed:" + status);
+ return a2dpCodecConfigList;
}
- for (Integer format : formatsList) {
- int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
- if (btSourceCodec
- != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
- codecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
+ if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ for (Integer format : formatsList) {
+ int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
+ if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
+ }
}
+ return a2dpCodecConfigList;
+ } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) {
+ for (Integer format : formatsList) {
+ int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format);
+ if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder()
+ .setCodecType(btLeAudioCodec)
+ .build());
+ }
+ }
+ return leAudioCodecConfigList;
}
- return codecConfigList;
+ Log.e(TAG, "Input deviceType " + deviceType + " doesn't support.");
+ return a2dpCodecConfigList;
}
// Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 16cb5f4..cc37c38 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -22,6 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -236,6 +237,9 @@
public static final int AUDIO_FORMAT_APTX_HD = 0x21000000;
/** @hide */
public static final int AUDIO_FORMAT_LDAC = 0x23000000;
+ /** @hide */
+ public static final int AUDIO_FORMAT_LC3 = 0x2B000000;
+
/** @hide */
@IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = {
@@ -245,11 +249,26 @@
AUDIO_FORMAT_SBC,
AUDIO_FORMAT_APTX,
AUDIO_FORMAT_APTX_HD,
- AUDIO_FORMAT_LDAC }
+ AUDIO_FORMAT_LDAC}
)
@Retention(RetentionPolicy.SOURCE)
public @interface AudioFormatNativeEnumForBtCodec {}
+ /** @hide */
+ @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = {
+ AUDIO_FORMAT_LC3}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioFormatNativeEnumForBtLeAudioCodec {}
+
+ /** @hide */
+ @IntDef(flag = false, prefix = "DEVICE_", value = {
+ DEVICE_OUT_BLUETOOTH_A2DP,
+ DEVICE_OUT_BLE_HEADSET}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceType {}
+
/**
* @hide
* Convert audio format enum values to Bluetooth codec values
@@ -271,6 +290,21 @@
/**
* @hide
+ * Convert audio format enum values to Bluetooth LE audio codec values
+ */
+ public static int audioFormatToBluetoothLeAudioSourceCodec(
+ @AudioFormatNativeEnumForBtLeAudioCodec int audioFormat) {
+ switch (audioFormat) {
+ case AUDIO_FORMAT_LC3: return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3;
+ default:
+ Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat)
+ + " for conversion to BT LE audio codec");
+ return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID;
+ }
+ }
+
+ /**
+ * @hide
* Convert a Bluetooth codec to an audio format enum
* @param btCodec the codec to convert.
* @return the audio format, or {@link #AUDIO_FORMAT_DEFAULT} if unknown
@@ -1761,10 +1795,10 @@
/**
* @hide
- * Returns a list of audio formats (codec) supported on the A2DP offload path.
+ * Returns a list of audio formats (codec) supported on the A2DP and LE audio offload path.
*/
- public static native int getHwOffloadEncodingFormatsSupportedForA2DP(
- ArrayList<Integer> formatList);
+ public static native int getHwOffloadFormatsSupportedForBluetoothMedia(
+ @DeviceType int deviceType, ArrayList<Integer> formatList);
/** @hide */
public static native int setSurroundFormatEnabled(int audioFormat, boolean enabled);
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index e979a1b5..5261555 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -17,9 +17,12 @@
package android.media;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
import android.hardware.HardwareBuffer;
import java.nio.ByteBuffer;
@@ -280,6 +283,31 @@
return;
}
+ private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+
+ /**
+ * Get the dataspace associated with this frame.
+ */
+ @SuppressLint("MethodNameUnits")
+ public @NamedDataSpace long getDataSpace() {
+ throwISEIfImageIsInvalid();
+ return mDataSpace;
+ }
+
+ /**
+ * Set the dataspace associated with this frame.
+ * <p>
+ * If dataspace for an image is not set, dataspace value depends on {@link android.view.Surface}
+ * that is provided in the {@link ImageWriter} constructor.
+ * </p>
+ *
+ * @param dataSpace The Dataspace to be set for this image
+ */
+ public void setDataSpace(@NamedDataSpace long dataSpace) {
+ throwISEIfImageIsInvalid();
+ mDataSpace = dataSpace;
+ }
+
private Rect mCropRect;
/**
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 5656dff..bd0f32e 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -16,7 +16,6 @@
package android.media;
-import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.graphics.GraphicBuffer;
@@ -28,7 +27,6 @@
import android.hardware.camera2.MultiResolutionImageReader;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.view.Surface;
import dalvik.system.VMRuntime;
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 1b74367..1fc2cf9 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -23,9 +23,9 @@
import android.graphics.ImageFormat.Format;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SurfaceUtils;
-import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -454,8 +454,9 @@
}
Rect crop = image.getCropRect();
- nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), crop.left, crop.top,
- crop.right, crop.bottom, image.getTransform(), image.getScalingMode());
+ nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
+ crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
+ image.getScalingMode());
/**
* Only remove and cleanup the Images that are owned by this
@@ -642,13 +643,13 @@
Rect crop = image.getCropRect();
if (image.getNativeContext() != 0) {
nativeAttachAndQueueImage(mNativeContext, image.getNativeContext(), image.getFormat(),
- image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
- image.getTransform(), image.getScalingMode());
+ image.getTimestamp(), image.getDataSpace(), crop.left, crop.top, crop.right,
+ crop.bottom, image.getTransform(), image.getScalingMode());
} else {
GraphicBuffer gb = GraphicBuffer.createFromHardwareBuffer(image.getHardwareBuffer());
nativeAttachAndQueueGraphicBuffer(mNativeContext, gb, image.getFormat(),
- image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
- image.getTransform(), image.getScalingMode());
+ image.getTimestamp(), image.getDataSpace(), crop.left, crop.top, crop.right,
+ crop.bottom, image.getTransform(), image.getScalingMode());
gb.destroy();
image.close();
}
@@ -976,15 +977,15 @@
private synchronized native void nativeDequeueInputImage(long nativeCtx, Image wi);
private synchronized native void nativeQueueInputImage(long nativeCtx, Image image,
- long timestampNs, int left, int top, int right, int bottom, int transform,
- int scalingMode);
+ long timestampNs, long dataSpace, int left, int top, int right, int bottom,
+ int transform, int scalingMode);
private synchronized native int nativeAttachAndQueueImage(long nativeCtx,
- long imageNativeBuffer, int imageFormat, long timestampNs, int left,
- int top, int right, int bottom, int transform, int scalingMode);
+ long imageNativeBuffer, int imageFormat, long timestampNs, long dataSpace,
+ int left, int top, int right, int bottom, int transform, int scalingMode);
private synchronized native int nativeAttachAndQueueGraphicBuffer(long nativeCtx,
- GraphicBuffer graphicBuffer, int imageFormat, long timestampNs, int left,
- int top, int right, int bottom, int transform, int scalingMode);
+ GraphicBuffer graphicBuffer, int imageFormat, long timestampNs, long dataSpace,
+ int left, int top, int right, int bottom, int transform, int scalingMode);
private synchronized native void cancelImage(long nativeCtx, Image image);
diff --git a/media/java/android/media/tv/AitInfo.aidl b/media/java/android/media/tv/AitInfo.aidl
new file mode 100644
index 0000000..b7d9fe8
--- /dev/null
+++ b/media/java/android/media/tv/AitInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+parcelable AitInfo;
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
new file mode 100644
index 0000000..5f8487d
--- /dev/null
+++ b/media/java/android/media/tv/AitInfo.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * AIT info.
+ * @hide
+ */
+public final class AitInfo implements Parcelable {
+ static final String TAG = "AitInfo";
+
+ public static final Creator<AitInfo> CREATOR = new Creator<AitInfo>() {
+ @Override
+ public AitInfo createFromParcel(Parcel in) {
+ return new AitInfo(in);
+ }
+
+ @Override
+ public AitInfo[] newArray(int size) {
+ return new AitInfo[size];
+ }
+ };
+
+ private final int mType;
+ private final int mVersion;
+
+ private AitInfo(Parcel in) {
+ mType = in.readInt();
+ mVersion = in.readInt();
+ }
+
+ /**
+ * Constructs AIT info.
+ */
+ public AitInfo(int type, int version) {
+ mType = type;
+ mVersion = version;
+ }
+
+ /**
+ * Gets type.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets version.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mVersion);
+ }
+
+ @Override
+ public String toString() {
+ return "type=" + mType + ";version=" + mVersion;
+ }
+}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 5dad633..6f7db4a 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,12 +17,13 @@
package android.media.tv;
import android.content.ComponentName;
+import android.media.tv.AitInfo;
+import android.media.tv.BroadcastInfoResponse;
import android.media.tv.ITvInputSession;
import android.net.Uri;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.view.InputChannel;
-import android.media.tv.BroadcastInfoResponse;
/**
* Interface a client of the ITvInputManager implements, to identify itself and receive information
@@ -44,9 +45,10 @@
void onTimeShiftStatusChanged(int status, int seq);
void onTimeShiftStartPositionChanged(long timeMs, int seq);
void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
+ void onAitInfoUpdated(in AitInfo aitInfo, int seq);
+ void onTuned(in Uri channelUri, int seq);
// For the recording session
- void onTuned(int seq, in Uri channelUri);
void onRecordingStopped(in Uri recordedProgramUri, int seq);
void onError(int error, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index eaf89ba..0070898 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.media.PlaybackParams;
+import android.media.tv.BroadcastInfoRequest;
import android.media.tv.DvbDeviceInfo;
import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
@@ -35,7 +36,6 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.view.Surface;
-import android.media.tv.BroadcastInfoRequest;
/**
* Interface to the TV input manager service.
@@ -73,6 +73,8 @@
void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
+ void setIAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
+
void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data,
int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 1eab650..984a551 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -18,11 +18,11 @@
import android.graphics.Rect;
import android.media.PlaybackParams;
+import android.media.tv.BroadcastInfoRequest;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.Surface;
-import android.media.tv.BroadcastInfoRequest;
/**
* Sub-interface of ITvInputService which is created per session and has its own context.
@@ -41,6 +41,8 @@
void setCaptionEnabled(boolean enabled);
void selectTrack(int type, in String trackId);
+ void setIAppNotificationEnabled(boolean enable);
+
void appPrivateCommand(in String action, in Bundle data);
void createOverlayView(in IBinder windowToken, in Rect frame);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index d857c00..9a0aaa3 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,11 +16,12 @@
package android.media.tv;
+import android.media.tv.AitInfo;
+import android.media.tv.BroadcastInfoResponse;
import android.media.tv.ITvInputSession;
import android.net.Uri;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
-import android.media.tv.BroadcastInfoResponse;
/**
* Helper interface for ITvInputSession to allow the TV input to notify the system service when a
@@ -41,6 +42,7 @@
void onTimeShiftStatusChanged(int status);
void onTimeShiftStartPositionChanged(long timeMs);
void onTimeShiftCurrentPositionChanged(long timeMs);
+ void onAitInfoUpdated(in AitInfo aitInfo);
// For the recording session
void onTuned(in Uri channelUri);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 4257145..6539472 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -71,6 +71,7 @@
private static final int DO_PAUSE_RECORDING = 22;
private static final int DO_RESUME_RECORDING = 23;
private static final int DO_REQUEST_BROADCAST_INFO = 24;
+ private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 25;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -239,6 +240,10 @@
mTvInputSessionImpl.requestBroadcastInfo((BroadcastInfoRequest) msg.obj);
break;
}
+ case DO_SET_IAPP_NOTIFICATION_ENABLED: {
+ mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -307,6 +312,12 @@
}
@Override
+ public void setIAppNotificationEnabled(boolean enabled) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SET_IAPP_NOTIFICATION_ENABLED, enabled));
+ }
+
+ @Override
public void appPrivateCommand(String action, Bundle data) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action,
data));
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index bafb03b..b655a61 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -627,14 +627,20 @@
public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
}
- // For the recording session only
/**
- * This is called when the recording session has been tuned to the given channel and is
- * ready to start recording.
+ * This is called when AIT info is updated.
+ * @param session A {@link TvInputManager.Session} associated with this callback.
+ * @param aitInfo The current AIT info.
+ */
+ public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
+ }
+
+ /**
+ * This is called when the session has been tuned to the given channel.
*
* @param channelUri The URI of a channel.
*/
- void onTuned(Session session, Uri channelUri) {
+ public void onTuned(Session session, Uri channelUri) {
}
// For the recording session only
@@ -807,12 +813,23 @@
});
}
- // For the recording session only
+ void postAitInfoUpdated(final AitInfo aitInfo) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onAitInfoUpdated(mSession, aitInfo);
+ }
+ });
+ }
+
void postTuned(final Uri channelUri) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onTuned(mSession, channelUri);
+ if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyTuned(channelUri);
+ }
}
});
}
@@ -838,12 +855,16 @@
}
void postBroadcastInfoResponse(final BroadcastInfoResponse response) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mSession.getIAppSession().notifyBroadcastInfoResponse(response);
- }
- });
+ if (mSession.mIAppNotificationEnabled) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyBroadcastInfoResponse(response);
+ }
+ }
+ });
+ }
}
}
@@ -1209,7 +1230,19 @@
}
@Override
- public void onTuned(int seq, Uri channelUri) {
+ public void onAitInfoUpdated(AitInfo aitInfo, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postAitInfoUpdated(aitInfo);
+ }
+ }
+
+ @Override
+ public void onTuned(Uri channelUri, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
@@ -1217,6 +1250,13 @@
return;
}
record.postTuned(channelUri);
+ // TODO: synchronized and wrap the channelUri
+ if (record.mSession.mIAppNotificationEnabled) {
+ TvIAppManager.Session iappSession = record.mSession.mIAppSession;
+ if (iappSession != null) {
+ iappSession.notifyTuned(channelUri);
+ }
+ }
}
}
@@ -2068,6 +2108,7 @@
private int mVideoHeight;
private TvIAppManager.Session mIAppSession;
+ private boolean mIAppNotificationEnabled = false;
private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
@@ -2340,6 +2381,25 @@
}
/**
+ * Enables interactive app notification.
+ * @param enabled {@code true} if you want to enable interactive app notifications.
+ * {@code false} otherwise.
+ * @hide
+ */
+ public void setIAppNotificationEnabled(boolean enabled) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.setIAppNotificationEnabled(mToken, enabled, mUserId);
+ mIAppNotificationEnabled = enabled;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Responds to onTracksChanged() and updates the internal track information. Returns true if
* there is an update.
*/
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index d285b13..6743dd6 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -828,6 +828,27 @@
}
/**
+ * Notifies AIT info updated.
+ * @hide
+ */
+ public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyAitInfoUpdated");
+ if (mSessionCallback != null) {
+ mSessionCallback.onAitInfoUpdated(aitInfo);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyAitInfoUpdated", e);
+ }
+ }
+ });
+ }
+
+ /**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
@@ -1021,6 +1042,14 @@
}
/**
+ * Enables or disables interactive app notification.
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ * @hide
+ */
+ public void onSetIAppNotificationEnabled(boolean enabled) {
+ }
+
+ /**
* Processes a private command sent from the application to the TV input. This can be used
* to provide domain-specific features that are only known between certain TV inputs and
* their clients.
@@ -1369,6 +1398,13 @@
}
/**
+ * Calls {@link #onSetIAppNotificationEnabled}.
+ */
+ void setIAppNotificationEnabled(boolean enabled) {
+ onSetIAppNotificationEnabled(enabled);
+ }
+
+ /**
* Calls {@link #onAppPrivateCommand}.
*/
void appPrivateCommand(String action, Bundle data) {
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 180e2bd..87d7a0b 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -472,7 +472,7 @@
}
@Override
- void onTuned(TvInputManager.Session session, Uri channelUri) {
+ public void onTuned(TvInputManager.Session session, Uri channelUri) {
if (DEBUG) {
Log.d(TAG, "onTuned()");
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6994d28..4a12cd7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -34,8 +34,6 @@
import android.media.tv.TvInputManager.Session;
import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
import android.media.tv.TvInputManager.SessionCallback;
-import android.media.tv.interactive.TvIAppManager;
-import android.media.tv.interactive.TvIAppView;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -483,6 +481,18 @@
}
/**
+ * Enables interactive app notification.
+ * @param enabled {@code true} if you want to enable interactive app notifications.
+ * {@code false} otherwise.
+ * @hide
+ */
+ public void setIAppNotificationEnabled(boolean enabled) {
+ if (mSession != null) {
+ mSession.setIAppNotificationEnabled(enabled);
+ }
+ }
+
+ /**
* Plays a given recorded TV program.
*
* @param inputId The ID of the TV input that created the given recorded program.
@@ -1050,6 +1060,24 @@
public void onTimeShiftStatusChanged(
String inputId, @TvInputManager.TimeShiftStatus int status) {
}
+
+ /**
+ * This is called when the AIT info has been updated.
+ *
+ * @param aitInfo The current AIT info.
+ * @hide
+ */
+ public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+ }
+
+ /**
+ * This is called when the session has been tuned to the given channel.
+ *
+ * @param channelUri The URI of a channel.
+ * @hide
+ */
+ public void onTuned(String inputId, Uri channelUri) {
+ }
}
/**
@@ -1346,5 +1374,33 @@
mTimeShiftPositionCallback.onTimeShiftCurrentPositionChanged(mInputId, timeMs);
}
}
+
+ @Override
+ public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
+ if (DEBUG) {
+ Log.d(TAG, "onAitInfoUpdated(aitInfo=" + aitInfo + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onAitInfoUpdated - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onAitInfoUpdated(mInputId, aitInfo);
+ }
+ }
+
+ @Override
+ public void onTuned(Session session, Uri channelUri) {
+ if (DEBUG) {
+ Log.d(TAG, "onTuned(channelUri=" + channelUri + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onTuned - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onTuned(mInputId, channelUri);
+ }
+ }
}
}
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 39c438a..9fc1fe7 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.media.tv.BroadcastInfoRequest;
+import android.media.tv.BroadcastInfoRequest;
import android.view.InputChannel;
/**
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 25e1ace..cd87a09 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -16,10 +16,12 @@
package android.media.tv.interactive;
+import android.graphics.Rect;
+import android.media.tv.BroadcastInfoResponse;
import android.media.tv.interactive.ITvIAppClient;
import android.media.tv.interactive.ITvIAppManagerCallback;
import android.media.tv.interactive.TvIAppInfo;
-import android.media.tv.BroadcastInfoResponse;
+import android.net.Uri;
import android.view.Surface;
/**
@@ -28,16 +30,23 @@
*/
interface ITvIAppManager {
List<TvIAppInfo> getTvIAppServiceList(int userId);
+ void prepare(String tiasId, int type, int userId);
void startIApp(in IBinder sessionToken, int userId);
void createSession(
in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
+ void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response,
int UserId);
+ void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeMediaView(in IBinder sessionToken, int userId);
+
void registerCallback(in ITvIAppManagerCallback callback, int userId);
void unregisterCallback(in ITvIAppManagerCallback callback, int userId);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
index 1dee9cc..af15dd8 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -30,4 +30,5 @@
void unregisterCallback(in ITvIAppServiceCallback callback);
void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
in String iAppServiceId, int type);
+ void prepare(int type);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
index 440b3d30..0d37a2b 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -16,6 +16,9 @@
package android.media.tv.interactive;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.media.tv.BroadcastInfoResponse;
import android.view.Surface;
import android.media.tv.BroadcastInfoResponse;
@@ -26,7 +29,12 @@
oneway interface ITvIAppSession {
void startIApp();
void release();
+ void notifyTuned(in Uri channelUri);
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
+
+ void createMediaView(in IBinder windowToken, in Rect frame);
+ void relayoutMediaView(in Rect frame);
+ void removeMediaView();
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
index d308463..d2b966e 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
@@ -16,6 +16,7 @@
package android.media.tv.interactive;
+import android.media.tv.BroadcastInfoRequest;
import android.media.tv.interactive.ITvIAppSession;
import android.media.tv.BroadcastInfoRequest;
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index ae35edc..fed9769 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -20,9 +20,11 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.graphics.Rect;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvInputManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -35,6 +37,7 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
+import android.view.View;
import com.android.internal.util.Preconditions;
@@ -332,6 +335,18 @@
}
/**
+ * Prepares TV IApp service for the given type.
+ * @hide
+ */
+ public void prepare(String tvIAppServiceId, int type) {
+ try {
+ mService.prepare(tvIAppServiceId, type, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Registers a {@link TvIAppManager.TvIAppCallback}.
*
* @param callback A callback used to monitor status of the TV IApp services.
@@ -443,6 +458,67 @@
}
/**
+ * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeMediaView()} should be called to remove the media view.
+ * Since a session can have only one media view, this method should be called only once
+ * or it can be called again after calling {@link #removeMediaView()}.
+ *
+ * @param view A view for interactive app.
+ * @param frame A position of the media view.
+ * @throws IllegalStateException if {@code view} is not attached to a window.
+ */
+ void createMediaView(@NonNull View view, @NonNull Rect frame) {
+ Preconditions.checkNotNull(view);
+ Preconditions.checkNotNull(frame);
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(@NonNull Rect frame) {
+ Preconditions.checkNotNull(frame);
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.relayoutMediaView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.removeMediaView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies of any structural changes (format or size) of the surface passed in
* {@link #setSurface}.
*
@@ -533,6 +609,21 @@
releaseInternal();
}
+ /**
+ * Notifies IAPP session when a channels is tuned.
+ */
+ public void notifyTuned(Uri channelUri) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTuned(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void flushPendingEventsLocked() {
mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index fe087ca..027b890 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -20,18 +20,25 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -39,6 +46,9 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
import com.android.internal.os.SomeArgs;
@@ -52,6 +62,8 @@
private static final boolean DEBUG = false;
private static final String TAG = "TvIAppService";
+ private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
// TODO: cleanup and unhide APIs.
/**
@@ -106,10 +118,23 @@
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
.sendToTarget();
}
+
+ @Override
+ public void prepare(int type) {
+ onPrepare(type);
+ }
};
return tvIAppServiceBinder;
}
+ /**
+ * Prepares TV IApp service for the given type.
+ * @hide
+ */
+ public void onPrepare(int type) {
+ // TODO: make it abstract when unhide
+ }
+
/**
* Returns a concrete implementation of {@link Session}.
@@ -141,8 +166,16 @@
private final List<Runnable> mPendingActions = new ArrayList<>();
private final Context mContext;
- private final Handler mHandler;
+ final Handler mHandler;
+ private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
+ private FrameLayout mMediaViewContainer;
+ private View mMediaView;
+ private MediaViewCleanUpTask mMediaViewCleanUpTask;
+ private boolean mMediaViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mMediaFrame;
/**
* Creates a new Session.
@@ -151,10 +184,41 @@
*/
public Session(Context context) {
mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler(context.getMainLooper());
}
/**
+ * Enables or disables the media view.
+ *
+ * <p>By default, the media view is disabled. Must be called explicitly after the
+ * session is created to enable the media view.
+ *
+ * <p>The TV IApp service can disable its media view when needed.
+ *
+ * @param enable {@code true} if you want to enable the media view. {@code false}
+ * otherwise.
+ */
+ public void setMediaViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mMediaViewEnabled) {
+ return;
+ }
+ mMediaViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createMediaView(mWindowToken, mMediaFrame);
+ }
+ } else {
+ removeMediaView(false);
+ }
+ }
+ });
+ }
+
+ /**
* Starts TvIAppService session.
* @hide
*/
@@ -187,11 +251,27 @@
}
/**
- * Called when a broadcast info response is received from TIS.
+ * Called when the size of the media view is changed by the application.
*
- * @param response response received from TIS.
+ * <p>This is always called at least once when the session is created regardless of whether
+ * the media view is enabled or not. The media view container size is the same as the
+ * containing {@link TvIAppView}. Note that the size of the underlying surface can be
+ * different if the surface was changed by calling {@link #layoutSurface}.
+ *
+ * @param width The width of the media view.
+ * @param height The height of the media view.
*/
- public void onNotifyBroadcastInfoResponse(BroadcastInfoResponse response) {
+ public void onMediaViewSizeChanged(int width, int height) {
+ }
+
+ /**
+ * Called when the application requests to create an media view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the media window
+ */
+ public View onCreateMediaView() {
+ return null;
}
/**
@@ -202,6 +282,20 @@
}
/**
+ * Called when the corresponding TV input tuned to a channel.
+ * @hide
+ */
+ public void onTuned(Uri channelUri) {
+ }
+
+ /**
+ * Called when a broadcast info response is received.
+ * @hide
+ */
+ public void onBroadcastInfoResponse(BroadcastInfoResponse response) {
+ }
+
+ /**
* TODO: JavaDoc of APIs related to input events.
* @hide
*/
@@ -288,6 +382,10 @@
});
}
+ /**
+ * Requests broadcast related information from the related TV input.
+ * @param request
+ */
public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@@ -318,6 +416,32 @@
mSurface.release();
mSurface = null;
}
+ synchronized (mLock) {
+ mSessionCallback = null;
+ mPendingActions.clear();
+ }
+ // Removes the media view lastly so that any hanging on the main thread can be handled
+ // in {@link #scheduleMediaViewCleanup}.
+ removeMediaView(true);
+ }
+
+ void notifyTuned(Uri channelUri) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTuned (channelUri=" + channelUri + ")");
+ }
+ onTuned(channelUri);
+ }
+
+
+ /**
+ * Calls {@link #onBroadcastInfoResponse}.
+ */
+ void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
+ + response.getRequestId() + ")");
+ }
+ onBroadcastInfoResponse(response);
}
/**
@@ -386,18 +510,6 @@
onSurfaceChanged(format, width, height);
}
- /**
- *
- * Calls {@link #notifyBroadcastInfoResponse}.
- */
- void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
- if (DEBUG) {
- Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
- + response.getRequestId() + ")");
- }
- onNotifyBroadcastInfoResponse(response);
- }
-
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
@@ -413,6 +525,137 @@
}
}
}
+
+ /**
+ * Creates an media view. This calls {@link #onCreateMediaView} to get a view to attach
+ * to the media window.
+ *
+ * @param windowToken A window token of the application.
+ * @param frame A position of the media view.
+ */
+ void createMediaView(IBinder windowToken, Rect frame) {
+ if (mMediaViewContainer != null) {
+ removeMediaView(false);
+ }
+ if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+ mWindowToken = windowToken;
+ mMediaFrame = frame;
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ if (!mMediaViewEnabled) {
+ return;
+ }
+ mMediaView = onCreateMediaView();
+ if (mMediaView == null) {
+ return;
+ }
+ if (mMediaViewCleanUpTask != null) {
+ mMediaViewCleanUpTask.cancel(true);
+ mMediaViewCleanUpTask = null;
+ }
+ // Creates a container view to check hanging on the media view detaching.
+ // Adding/removing the media view to/from the container make the view attach/detach
+ // logic run on the main thread.
+ mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+ mMediaViewContainer.addView(mMediaView);
+
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(Rect frame) {
+ if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+ if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+ || mMediaFrame.height() != frame.height()) {
+ // Note: relayoutMediaView is called whenever TvIAppView's layout is changed
+ // regardless of setMediaViewEnabled.
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ }
+ mMediaFrame = frame;
+ if (!mMediaViewEnabled || mMediaViewContainer == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView(boolean clearWindowToken) {
+ if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mMediaFrame = null;
+ }
+ if (mMediaViewContainer != null) {
+ // Removes the media view from the view hierarchy in advance so that it can be
+ // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+ // hanging.
+ mMediaViewContainer.removeView(mMediaView);
+ mMediaView = null;
+ mWindowManager.removeView(mMediaViewContainer);
+ mMediaViewContainer = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Schedules a task which checks whether the media view is detached and kills the process
+ * if it is not. Note that this method is expected to be called in a non-main thread.
+ */
+ void scheduleMediaViewCleanup() {
+ View mediaViewParent = mMediaViewContainer;
+ if (mediaViewParent != null) {
+ mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+ mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ mediaViewParent);
+ }
+ }
+ }
+
+ private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+ @Override
+ protected Void doInBackground(View... views) {
+ View mediaViewParent = views[0];
+ try {
+ Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ if (isCancelled()) {
+ return null;
+ }
+ if (mediaViewParent.isAttachedToWindow()) {
+ Log.e(TAG, "Time out on releasing media view. Killing "
+ + mediaViewParent.getContext().getPackageName());
+ android.os.Process.killProcess(Process.myPid());
+ }
+ return null;
+ }
}
/**
@@ -440,10 +683,16 @@
@Override
public void release() {
+ mSessionImpl.scheduleMediaViewCleanup();
mSessionImpl.release();
}
@Override
+ public void notifyTuned(Uri channelUri) {
+ mSessionImpl.notifyTuned(channelUri);
+ }
+
+ @Override
public void setSurface(Surface surface) {
mSessionImpl.setSurface(surface);
}
@@ -458,6 +707,21 @@
mSessionImpl.notifyBroadcastInfoResponse(response);
}
+ @Override
+ public void createMediaView(IBinder windowToken, Rect frame) {
+ mSessionImpl.createMediaView(windowToken, frame);
+ }
+
+ @Override
+ public void relayoutMediaView(Rect frame) {
+ mSessionImpl.relayoutMediaView(frame);
+ }
+
+ @Override
+ public void removeMediaView() {
+ mSessionImpl.removeMediaView(true);
+ }
+
private final class TvIAppEventReceiver extends InputEventReceiver {
TvIAppEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java
index 1b25c23..8031981 100644
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvIAppView.java
@@ -20,6 +20,8 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.media.tv.TvInputManager;
import android.media.tv.TvView;
import android.media.tv.interactive.TvIAppManager.Session;
@@ -65,6 +67,9 @@
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
+ private boolean mMediaViewCreated;
+ private Rect mMediaViewFrame;
+
private final AttributeSet mAttrs;
private final int mDefStyleAttr;
private final XmlResourceParser mParser;
@@ -119,6 +124,18 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionMediaView();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeSessionMediaView();
+ super.onDetachedFromWindow();
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
@@ -147,6 +164,11 @@
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ createSessionMediaView();
+ } else {
+ removeSessionMediaView();
+ }
}
private void resetSurfaceView() {
@@ -155,7 +177,12 @@
removeView(mSurfaceView);
}
mSurface = null;
- mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr);
+ mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+ @Override
+ protected void updateSurface() {
+ super.updateSurface();
+ relayoutSessionMediaView();
+ }};
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
@@ -170,6 +197,46 @@
resetInternal();
}
+ private void createSessionMediaView() {
+ // TODO: handle z-order
+ if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+ return;
+ }
+ mMediaViewFrame = getViewFrameOnScreen();
+ mSession.createMediaView(this, mMediaViewFrame);
+ mMediaViewCreated = true;
+ }
+
+ private void removeSessionMediaView() {
+ if (mSession == null || !mMediaViewCreated) {
+ return;
+ }
+ mSession.removeMediaView();
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
+ }
+
+ private void relayoutSessionMediaView() {
+ if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mMediaViewFrame)) {
+ return;
+ }
+ mSession.relayoutMediaView(viewFrame);
+ mMediaViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ Rect frame = new Rect();
+ getGlobalVisibleRect(frame);
+ RectF frameF = new RectF(frame);
+ getMatrix().mapRect(frameF);
+ frameF.round(frame);
+ return frame;
+ }
+
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
@@ -214,6 +281,7 @@
mSessionCallback = null;
if (mSession != null) {
setSessionSurface(null);
+ removeSessionMediaView();
mUseRequestedSurfaceLayout = false;
mSession.release();
mSession = null;
@@ -287,6 +355,7 @@
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
+ createSessionMediaView();
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -303,6 +372,8 @@
Log.w(TAG, "onSessionReleased - session not created");
return;
}
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
mSessionCallback = null;
mSession = null;
}
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 5174c0c..021507c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -48,6 +48,7 @@
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mNativeBuffer"
#define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID "mTimestamp"
+#define ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID "mDataSpace"
#define ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID "mTransform"
#define ANDROID_MEDIA_SURFACEIMAGE_SM_JNI_ID "mScalingMode"
@@ -71,6 +72,7 @@
static struct {
jfieldID mNativeBuffer;
jfieldID mTimestamp;
+ jfieldID mDataSpace;
jfieldID mTransform;
jfieldID mScalingMode;
jfieldID mPlanes;
@@ -319,6 +321,12 @@
"can't find android/graphics/ImageReader.%s",
ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID);
+ gSurfaceImageClassInfo.mDataSpace = env->GetFieldID(
+ imageClazz, ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID, "J");
+ LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mDataSpace == NULL,
+ "can't find android/graphics/ImageReader.%s",
+ ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID);
+
gSurfaceImageClassInfo.mTransform = env->GetFieldID(
imageClazz, ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID, "I");
LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mTransform == NULL,
@@ -619,6 +627,8 @@
Image_setBufferItem(env, image, buffer);
env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
static_cast<jlong>(buffer->mTimestamp));
+ env->SetLongField(image, gSurfaceImageClassInfo.mDataSpace,
+ static_cast<jlong>(buffer->mDataSpace));
auto transform = buffer->mTransform;
if (buffer->mTransformToDisplayInverse) {
transform |= NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index b291ac95b..0a5490d 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -53,6 +53,7 @@
} gImageWriterClassInfo;
static struct {
+ jfieldID mDataSpace;
jfieldID mNativeBuffer;
jfieldID mNativeFenceFd;
jfieldID mPlanes;
@@ -87,6 +88,9 @@
void setBufferHeight(int height) { mHeight = height; }
int getBufferHeight() { return mHeight; }
+ void setBufferDataSpace(android_dataspace dataSpace) { mDataSpace = dataSpace; }
+ android_dataspace getBufferDataSpace() { return mDataSpace; }
+
void queueAttachedFlag(bool isAttached) {
Mutex::Autolock l(mAttachedFlagQueueLock);
mAttachedFlagQueue.push_back(isAttached);
@@ -105,6 +109,7 @@
int mFormat;
int mWidth;
int mHeight;
+ android_dataspace mDataSpace;
// Class for a shared thread used to detach buffers from buffer queues
// to discard buffers after consumers are done using them.
@@ -316,7 +321,7 @@
// -------------------------------Private method declarations--------------
static void Image_setNativeContext(JNIEnv* env, jobject thiz,
- sp<GraphicBuffer> buffer, int fenceFd);
+ sp<GraphicBuffer> buffer, int fenceFd, long dataSpace);
static void Image_getNativeContext(JNIEnv* env, jobject thiz,
GraphicBuffer** buffer, int* fenceFd);
static void Image_unlockIfLocked(JNIEnv* env, jobject thiz);
@@ -328,6 +333,12 @@
jclass imageClazz = env->FindClass("android/media/ImageWriter$WriterSurfaceImage");
LOG_ALWAYS_FATAL_IF(imageClazz == NULL,
"can't find android/media/ImageWriter$WriterSurfaceImage");
+
+ gSurfaceImageClassInfo.mDataSpace = env->GetFieldID(
+ imageClazz, "mDataSpace", "J");
+ LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mDataSpace == NULL,
+ "can't find android/media/ImageWriter$WriterSurfaceImage.mDataSpace");
+
gSurfaceImageClassInfo.mNativeBuffer = env->GetFieldID(
imageClazz, IMAGE_BUFFER_JNI_ID, "J");
LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mNativeBuffer == NULL,
@@ -465,6 +476,7 @@
jniThrowRuntimeException(env, "Failed to set Surface dataspace");
return 0;
}
+ ctx->setBufferDataSpace(nativeDataspace);
surfaceFormat = userFormat;
}
@@ -544,7 +556,7 @@
// 3. need use lockAsync here, as it will handle the dequeued fence for us automatically.
// Finally, set the native info into image object.
- Image_setNativeContext(env, image, buffer, fenceFd);
+ Image_setNativeContext(env, image, buffer, fenceFd, ctx->getBufferDataSpace());
}
static void ImageWriter_close(JNIEnv* env, jobject thiz, jlong nativeCtx) {
@@ -605,12 +617,12 @@
anw->cancelBuffer(anw.get(), buffer, fenceFd);
- Image_setNativeContext(env, image, NULL, -1);
+ Image_setNativeContext(env, image, NULL, -1, HAL_DATASPACE_UNKNOWN);
}
static void ImageWriter_queueImage(JNIEnv* env, jobject thiz, jlong nativeCtx, jobject image,
- jlong timestampNs, jint left, jint top, jint right, jint bottom, jint transform,
- jint scalingMode) {
+ jlong timestampNs, jlong dataSpace, jint left, jint top, jint right,
+ jint bottom, jint transform, jint scalingMode) {
ALOGV("%s", __FUNCTION__);
JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
if (ctx == NULL || thiz == NULL) {
@@ -642,6 +654,15 @@
return;
}
+ // Set dataSpace
+ ALOGV("dataSpace to be queued: %" PRId64, dataSpace);
+ res = native_window_set_buffers_data_space(
+ anw.get(), static_cast<android_dataspace>(dataSpace));
+ if (res != OK) {
+ jniThrowRuntimeException(env, "Set dataspace failed");
+ return;
+ }
+
// Set crop
android_native_rect_t cropRect;
cropRect.left = left;
@@ -689,12 +710,12 @@
}
// Clear the image native context: end of this image's lifecycle in public API.
- Image_setNativeContext(env, image, NULL, -1);
+ Image_setNativeContext(env, image, NULL, -1, HAL_DATASPACE_UNKNOWN);
}
static status_t attachAndQeueuGraphicBuffer(JNIEnv* env, JNIImageWriterContext *ctx,
- sp<Surface> surface, sp<GraphicBuffer> gb, jlong timestampNs, jint left, jint top,
- jint right, jint bottom, jint transform, jint scalingMode) {
+ sp<Surface> surface, sp<GraphicBuffer> gb, jlong timestampNs, jlong dataSpace,
+ jint left, jint top, jint right, jint bottom, jint transform, jint scalingMode) {
status_t res = OK;
// Step 1. Attach Image
res = surface->attachBuffer(gb.get());
@@ -713,8 +734,8 @@
}
sp < ANativeWindow > anw = surface;
- // Step 2. Set timestamp, crop, transform and scaling mode. Note that we do not need unlock the
- // image because it was not locked.
+ // Step 2. Set timestamp, dataspace, crop, transform and scaling mode.
+ // Note that we do not need unlock the image because it was not locked.
ALOGV("timestamp to be queued: %" PRId64, timestampNs);
res = native_window_set_buffers_timestamp(anw.get(), timestampNs);
if (res != OK) {
@@ -722,6 +743,14 @@
return res;
}
+ ALOGV("dataSpace to be queued: %" PRId64, dataSpace);
+ res = native_window_set_buffers_data_space(
+ anw.get(), static_cast<android_dataspace>(dataSpace));
+ if (res != OK) {
+ jniThrowRuntimeException(env, "Set dataSpace failed");
+ return res;
+ }
+
android_native_rect_t cropRect;
cropRect.left = left;
cropRect.top = top;
@@ -775,8 +804,8 @@
}
static jint ImageWriter_attachAndQueueImage(JNIEnv* env, jobject thiz, jlong nativeCtx,
- jlong nativeBuffer, jint imageFormat, jlong timestampNs, jint left, jint top,
- jint right, jint bottom, jint transform, jint scalingMode) {
+ jlong nativeBuffer, jint imageFormat, jlong timestampNs, jlong dataSpace,
+ jint left, jint top, jint right, jint bottom, jint transform, jint scalingMode) {
ALOGV("%s", __FUNCTION__);
JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
if (ctx == NULL || thiz == NULL) {
@@ -801,12 +830,12 @@
return -1;
}
- return attachAndQeueuGraphicBuffer(env, ctx, surface, buffer->mGraphicBuffer, timestampNs, left,
- top, right, bottom, transform, scalingMode);
+ return attachAndQeueuGraphicBuffer(env, ctx, surface, buffer->mGraphicBuffer, timestampNs,
+ dataSpace, left, top, right, bottom, transform, scalingMode);
}
static jint ImageWriter_attachAndQueueGraphicBuffer(JNIEnv* env, jobject thiz, jlong nativeCtx,
- jobject buffer, jint format, jlong timestampNs, jint left, jint top,
+ jobject buffer, jint format, jlong timestampNs, jlong dataSpace, jint left, jint top,
jint right, jint bottom, jint transform, jint scalingMode) {
ALOGV("%s", __FUNCTION__);
JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
@@ -830,9 +859,8 @@
"Trying to attach an invalid graphic buffer");
return -1;
}
-
- return attachAndQeueuGraphicBuffer(env, ctx, surface, graphicBuffer, timestampNs, left,
- top, right, bottom, transform, scalingMode);
+ return attachAndQeueuGraphicBuffer(env, ctx, surface, graphicBuffer, timestampNs,
+ dataSpace, left, top, right, bottom, transform, scalingMode);
}
// --------------------------Image methods---------------------------------------
@@ -853,7 +881,7 @@
}
static void Image_setNativeContext(JNIEnv* env, jobject thiz,
- sp<GraphicBuffer> buffer, int fenceFd) {
+ sp<GraphicBuffer> buffer, int fenceFd, long dataSpace) {
ALOGV("%s:", __FUNCTION__);
GraphicBuffer* p = NULL;
Image_getNativeContext(env, thiz, &p, /*fenceFd*/NULL);
@@ -867,6 +895,8 @@
reinterpret_cast<jlong>(buffer.get()));
env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
+
+ env->SetLongField(thiz, gSurfaceImageClassInfo.mDataSpace, dataSpace);
}
static void Image_unlockIfLocked(JNIEnv* env, jobject thiz) {
@@ -1066,12 +1096,15 @@
{"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIII)J",
(void*)ImageWriter_init },
{"nativeClose", "(J)V", (void*)ImageWriter_close },
- {"nativeAttachAndQueueImage", "(JJIJIIIIII)I", (void*)ImageWriter_attachAndQueueImage },
+ {"nativeAttachAndQueueImage",
+ "(JJIJJIIIIII)I",
+ (void*)ImageWriter_attachAndQueueImage },
{"nativeAttachAndQueueGraphicBuffer",
- "(JLandroid/graphics/GraphicBuffer;IJIIIIII)I",
+ "(JLandroid/graphics/GraphicBuffer;IJJIIIIII)I",
(void*)ImageWriter_attachAndQueueGraphicBuffer },
{"nativeDequeueInputImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_dequeueImage },
- {"nativeQueueInputImage", "(JLandroid/media/Image;JIIIIII)V", (void*)ImageWriter_queueImage },
+ {"nativeQueueInputImage", "(JLandroid/media/Image;JJIIIIII)V",
+ (void*)ImageWriter_queueImage },
{"cancelImage", "(JLandroid/media/Image;)V", (void*)ImageWriter_cancelImage },
};
diff --git a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
index 48be6fe..669ad62 100644
--- a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
+++ b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
@@ -132,7 +132,7 @@
StorageVolume mainStorage = new StorageVolume(MAIN_STORAGE_ID_STR,
mMainStorageDir, mMainStorageDir, "Primary Storage",
- true, false, true, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
+ true, false, true, false, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
final StorageVolume primary = mainStorage;
diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
index fdf6582..abdc7e5 100644
--- a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
+++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
@@ -132,9 +132,10 @@
secondaryStorageDir = createNewDir(TEMP_DIR_FILE);
StorageVolume mainStorage = new StorageVolume("1", mainStorageDir, mainStorageDir,
- "", true, false, true, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
+ "", true, false, true, false, false, -1, UserHandle.CURRENT, null /* uuid */, "",
+ "");
StorageVolume secondaryStorage = new StorageVolume("2", secondaryStorageDir,
- secondaryStorageDir, "", false, false, true, false, -1, UserHandle.CURRENT,
+ secondaryStorageDir, "", false, false, true, false, false, -1, UserHandle.CURRENT,
null /* uuid */, "", "");
objectsAdded = new ArrayList<>();
diff --git a/packages/Nsd/OWNERS b/packages/Nsd/OWNERS
new file mode 100644
index 0000000..4862377
--- /dev/null
+++ b/packages/Nsd/OWNERS
@@ -0,0 +1 @@
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
diff --git a/packages/Nsd/framework/Android.bp b/packages/Nsd/framework/Android.bp
new file mode 100644
index 0000000..2363a9f
--- /dev/null
+++ b/packages/Nsd/framework/Android.bp
@@ -0,0 +1,54 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "framework-connectivity-nsd-internal-sources",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+filegroup {
+ name: "framework-connectivity-nsd-aidl-export-sources",
+ srcs: [
+ "aidl-export/**/*.aidl",
+ ],
+ path: "aidl-export",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+filegroup {
+ name: "framework-connectivity-nsd-sources",
+ srcs: [
+ ":framework-connectivity-nsd-internal-sources",
+ ":framework-connectivity-nsd-aidl-export-sources",
+ ],
+ visibility: [
+ "//frameworks/base",
+ ],
+}
diff --git a/core/java/android/net/nsd/NsdServiceInfo.aidl b/packages/Nsd/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
similarity index 100%
rename from core/java/android/net/nsd/NsdServiceInfo.aidl
rename to packages/Nsd/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
diff --git a/core/java/android/net/nsd/INsdManager.aidl b/packages/Nsd/framework/src/android/net/nsd/INsdManager.aidl
similarity index 100%
rename from core/java/android/net/nsd/INsdManager.aidl
rename to packages/Nsd/framework/src/android/net/nsd/INsdManager.aidl
diff --git a/core/java/android/net/nsd/INsdManagerCallback.aidl b/packages/Nsd/framework/src/android/net/nsd/INsdManagerCallback.aidl
similarity index 100%
rename from core/java/android/net/nsd/INsdManagerCallback.aidl
rename to packages/Nsd/framework/src/android/net/nsd/INsdManagerCallback.aidl
diff --git a/core/java/android/net/nsd/INsdServiceConnector.aidl b/packages/Nsd/framework/src/android/net/nsd/INsdServiceConnector.aidl
similarity index 100%
rename from core/java/android/net/nsd/INsdServiceConnector.aidl
rename to packages/Nsd/framework/src/android/net/nsd/INsdServiceConnector.aidl
diff --git a/core/java/android/net/nsd/NsdManager.java b/packages/Nsd/framework/src/android/net/nsd/NsdManager.java
similarity index 100%
rename from core/java/android/net/nsd/NsdManager.java
rename to packages/Nsd/framework/src/android/net/nsd/NsdManager.java
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/packages/Nsd/framework/src/android/net/nsd/NsdServiceInfo.java
similarity index 100%
rename from core/java/android/net/nsd/NsdServiceInfo.java
rename to packages/Nsd/framework/src/android/net/nsd/NsdServiceInfo.java
diff --git a/packages/Nsd/service/Android.bp b/packages/Nsd/service/Android.bp
new file mode 100644
index 0000000..529f58d
--- /dev/null
+++ b/packages/Nsd/service/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "services.connectivity-nsd-sources",
+ srcs: [
+ "src/**/*.java",
+ ],
+ path: "src",
+ visibility: [
+ "//frameworks/base/services/core",
+ ],
+}
diff --git a/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java b/packages/Nsd/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
similarity index 100%
rename from services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
rename to packages/Nsd/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
similarity index 95%
rename from services/core/java/com/android/server/NativeDaemonConnector.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
index eac767f..02475dd 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
@@ -26,12 +26,10 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.LocalLog;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.server.power.ShutdownThread;
-import com.google.android.collect.Lists;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -40,19 +38,19 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.LinkedList;
+import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.LinkedList;
-import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Generic connector class for interfacing with a native daemon which uses the
* {@code libsysutils} FrameworkListener protocol.
*/
-final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
+final class NativeDaemonConnector implements Runnable, Handler.Callback {
private final static boolean VDBG = false;
private final String TAG;
@@ -107,7 +105,7 @@
/**
* Enable Set debugging mode, which causes messages to also be written to both
- * {@link Slog} in addition to internal log.
+ * {@link Log} in addition to internal log.
*/
public void setDebug(boolean debug) {
mDebug = debug;
@@ -126,7 +124,9 @@
* calls while holding a lock on the given object.
*/
public void setWarnIfHeld(Object warnIfHeld) {
- Preconditions.checkState(mWarnIfHeld == null);
+ if (mWarnIfHeld != null) {
+ throw new IllegalStateException("warnIfHeld is already set.");
+ }
mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
}
@@ -183,7 +183,7 @@
// In order to ensure that unprivileged apps aren't able to impersonate native daemons on
// production devices, even if said native daemons ill-advisedly pick a socket name that
// starts with __test__, only allow this on debug builds.
- if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
+ if (mSocket.startsWith("__test__") && Build.isDebuggable()) {
return new LocalSocketAddress(mSocket);
} else {
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
@@ -375,7 +375,7 @@
try {
latch.await();
} catch (InterruptedException e) {
- Slog.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
+ Log.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
}
}
@@ -462,13 +462,13 @@
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
throws NativeDaemonConnectorException {
if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
- Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ Log.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
}
final long startTime = SystemClock.elapsedRealtime();
- final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+ final ArrayList<NativeDaemonEvent> events = new ArrayList<>();
final StringBuilder rawBuilder = new StringBuilder();
final StringBuilder logBuilder = new StringBuilder();
@@ -571,7 +571,7 @@
*/
public static class Command {
private String mCmd;
- private ArrayList<Object> mArguments = Lists.newArrayList();
+ private ArrayList<Object> mArguments = new ArrayList<>();
public Command(String cmd, Object... args) {
mCmd = cmd;
@@ -586,11 +586,6 @@
}
}
- /** {@inheritDoc} */
- public void monitor() {
- synchronized (mDaemonLock) { }
- }
-
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mLocalLog.dump(fd, pw, args);
pw.println();
@@ -598,12 +593,12 @@
}
private void log(String logstring) {
- if (mDebug) Slog.d(TAG, logstring);
+ if (mDebug) Log.d(TAG, logstring);
mLocalLog.log(logstring);
}
private void loge(String logstring) {
- Slog.e(TAG, logstring);
+ Log.e(TAG, logstring);
mLocalLog.log(logstring);
}
@@ -659,12 +654,12 @@
if (found == null) {
// didn't find it - make sure our queue isn't too big before adding
while (mPendingCmds.size() >= mMaxCount) {
- Slog.e("NativeDaemonConnector.ResponseQueue",
+ Log.e("NativeDaemonConnector.ResponseQueue",
"more buffered than allowed: " + mPendingCmds.size() +
" >= " + mMaxCount);
// let any waiter timeout waiting for this
PendingCmd pendingCmd = mPendingCmds.remove();
- Slog.e("NativeDaemonConnector.ResponseQueue",
+ Log.e("NativeDaemonConnector.ResponseQueue",
"Removing request: " + pendingCmd.logCmd + " (" +
pendingCmd.cmdNum + ")");
}
@@ -706,7 +701,7 @@
result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {}
if (result == null) {
- Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
+ Log.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
}
return result;
}
diff --git a/services/core/java/com/android/server/NativeDaemonConnectorException.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnectorException.java
similarity index 100%
rename from services/core/java/com/android/server/NativeDaemonConnectorException.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonConnectorException.java
diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
similarity index 93%
rename from services/core/java/com/android/server/NativeDaemonEvent.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
index e6feda3..5683694 100644
--- a/services/core/java/com/android/server/NativeDaemonEvent.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
@@ -16,8 +16,7 @@
package com.android.server;
-import android.util.Slog;
-import com.google.android.collect.Lists;
+import android.util.Log;
import java.io.FileDescriptor;
import java.util.ArrayList;
@@ -179,7 +178,7 @@
* {@link #getMessage()} for any events matching the requested code.
*/
public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
- final ArrayList<String> result = Lists.newArrayList();
+ final ArrayList<String> result = new ArrayList<>();
for (NativeDaemonEvent event : events) {
if (event.getCode() == matchCode) {
result.add(event.getMessage());
@@ -212,7 +211,7 @@
int wordEnd = -1;
boolean quoted = false;
- if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'");
+ if (DEBUG_ROUTINE) Log.e(LOGTAG, "parsing '" + rawEvent + "'");
if (rawEvent.charAt(current) == '\"') {
quoted = true;
current++;
@@ -240,14 +239,14 @@
word = word.replace("\\\\", "\\");
word = word.replace("\\\"", "\"");
- if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'");
+ if (DEBUG_ROUTINE) Log.e(LOGTAG, "found '" + word + "'");
parsed.add(word);
// find the beginning of the next word - either of these options
int nextSpace = rawEvent.indexOf(' ', current);
int nextQuote = rawEvent.indexOf(" \"", current);
if (DEBUG_ROUTINE) {
- Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
+ Log.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
}
if (nextQuote > -1 && nextQuote <= nextSpace) {
quoted = true;
@@ -259,8 +258,8 @@
}
} // else we just start the next word after the current and read til the end
if (DEBUG_ROUTINE) {
- Slog.e(LOGTAG, "next loop - current=" + current +
- ", length=" + length + ", quoted=" + quoted);
+ Log.e(LOGTAG, "next loop - current=" + current
+ + ", length=" + length + ", quoted=" + quoted);
}
}
return parsed.toArray(new String[parsed.size()]);
diff --git a/services/core/java/com/android/server/NativeDaemonTimeoutException.java b/packages/Nsd/service/src/com/android/server/NativeDaemonTimeoutException.java
similarity index 100%
rename from services/core/java/com/android/server/NativeDaemonTimeoutException.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonTimeoutException.java
diff --git a/services/core/java/com/android/server/NsdService.java b/packages/Nsd/service/src/com/android/server/NsdService.java
similarity index 94%
rename from services/core/java/com/android/server/NsdService.java
rename to packages/Nsd/service/src/com/android/server/NsdService.java
index 3e02084..76ecea7 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/packages/Nsd/service/src/com/android/server/NsdService.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.net.nsd.INsdManager;
@@ -36,12 +37,10 @@
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.net.module.util.DnsSdTxtRecord;
@@ -228,7 +227,7 @@
break;
case NsdManager.NATIVE_DAEMON_EVENT:
default:
- Slog.e(TAG, "Unhandled " + msg);
+ Log.e(TAG, "Unhandled " + msg);
return NOT_HANDLED;
}
return HANDLED;
@@ -274,7 +273,7 @@
private boolean requestLimitReached(ClientInfo clientInfo) {
if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
- if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
+ if (DBG) Log.d(TAG, "Exceeded max outstanding requests " + clientInfo);
return true;
}
return false;
@@ -307,7 +306,7 @@
transitionTo(mDisabledState);
break;
case NsdManager.DISCOVER_SERVICES:
- if (DBG) Slog.d(TAG, "Discover services");
+ if (DBG) Log.d(TAG, "Discover services");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
@@ -321,8 +320,8 @@
id = getUniqueId();
if (discoverServices(id, args.serviceInfo.getServiceType())) {
if (DBG) {
- Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
- args.serviceInfo.getServiceType());
+ Log.d(TAG, "Discover " + msg.arg2 + " " + id
+ + args.serviceInfo.getServiceType());
}
storeRequestMap(clientId, id, clientInfo, msg.what);
clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
@@ -333,7 +332,7 @@
}
break;
case NsdManager.STOP_DISCOVERY:
- if (DBG) Slog.d(TAG, "Stop service discovery");
+ if (DBG) Log.d(TAG, "Stop service discovery");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
@@ -353,7 +352,7 @@
}
break;
case NsdManager.REGISTER_SERVICE:
- if (DBG) Slog.d(TAG, "Register service");
+ if (DBG) Log.d(TAG, "Register service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
if (requestLimitReached(clientInfo)) {
@@ -365,7 +364,7 @@
maybeStartDaemon();
id = getUniqueId();
if (registerService(id, args.serviceInfo)) {
- if (DBG) Slog.d(TAG, "Register " + clientId + " " + id);
+ if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
storeRequestMap(clientId, id, clientInfo, msg.what);
// Return success after mDns reports success
} else {
@@ -375,11 +374,11 @@
}
break;
case NsdManager.UNREGISTER_SERVICE:
- if (DBG) Slog.d(TAG, "unregister service");
+ if (DBG) Log.d(TAG, "unregister service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
if (clientInfo == null) {
- Slog.e(TAG, "Unknown connector in unregistration");
+ Log.e(TAG, "Unknown connector in unregistration");
break;
}
id = clientInfo.mClientIds.get(clientId);
@@ -392,7 +391,7 @@
}
break;
case NsdManager.RESOLVE_SERVICE:
- if (DBG) Slog.d(TAG, "Resolve service");
+ if (DBG) Log.d(TAG, "Resolve service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
@@ -430,7 +429,7 @@
ClientInfo clientInfo = mIdToClientInfoMap.get(id);
if (clientInfo == null) {
String name = NativeResponseCode.nameOf(code);
- Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name));
+ Log.e(TAG, String.format("id %d for %s has no client mapping", id, name));
return false;
}
@@ -441,14 +440,14 @@
// SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
// and we may get in this situation.
String name = NativeResponseCode.nameOf(code);
- Slog.d(TAG, String.format(
+ Log.d(TAG, String.format(
"Notification %s for listener id %d that is no longer active",
name, id));
return false;
}
if (DBG) {
String name = NativeResponseCode.nameOf(code);
- Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
+ Log.d(TAG, String.format("Native daemon message %s: %s", name, raw));
}
switch (code) {
case NativeResponseCode.SERVICE_FOUND:
@@ -492,7 +491,7 @@
++index;
}
if (index >= cooked[2].length()) {
- Slog.e(TAG, "Invalid service found " + raw);
+ Log.e(TAG, "Invalid service found " + raw);
break;
}
String name = cooked[2].substring(0, index);
@@ -562,13 +561,13 @@
char c = s.charAt(i);
if (c == '\\') {
if (++i >= s.length()) {
- Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
break;
}
c = s.charAt(i);
if (c != '.' && c != '\\') {
if (i + 2 >= s.length()) {
- Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
break;
}
c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
@@ -685,7 +684,7 @@
private boolean isNsdEnabled() {
boolean ret = mNsdSettings.isEnabled();
if (DBG) {
- Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
+ Log.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
}
return ret;
}
@@ -795,12 +794,12 @@
*/
public boolean execute(Object... args) {
if (DBG) {
- Slog.d(TAG, "mdnssd " + Arrays.toString(args));
+ Log.d(TAG, "mdnssd " + Arrays.toString(args));
}
try {
mNativeConnector.execute("mdnssd", args);
} catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
+ Log.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
return false;
}
return true;
@@ -831,7 +830,7 @@
private boolean registerService(int regId, NsdServiceInfo service) {
if (DBG) {
- Slog.d(TAG, "registerService: " + regId + " " + service);
+ Log.d(TAG, "registerService: " + regId + " " + service);
}
String name = service.getServiceName();
String type = service.getServiceType();
@@ -880,7 +879,12 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump " + TAG
+ + " due to missing android.permission.DUMP permission");
+ return;
+ }
for (ClientInfo client : mClients.values()) {
pw.println("Client Info");
@@ -909,7 +913,7 @@
private ClientInfo(INsdManagerCallback cb) {
mCb = cb;
- if (DBG) Slog.d(TAG, "New client");
+ if (DBG) Log.d(TAG, "New client");
}
@Override
@@ -943,8 +947,10 @@
clientId = mClientIds.keyAt(i);
globalId = mClientIds.valueAt(i);
mIdToClientInfoMap.remove(globalId);
- if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
- " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+ if (DBG) {
+ Log.d(TAG, "Terminating client-ID " + clientId
+ + " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+ }
switch (mClientRequests.get(clientId)) {
case NsdManager.DISCOVER_SERVICES:
stopServiceDiscovery(globalId);
diff --git a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java b/packages/Nsd/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
rename to packages/Nsd/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f637ff8..d73e45e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -19,7 +19,6 @@
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -44,6 +43,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
import com.android.launcher3.icons.IconFactory;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -525,9 +525,9 @@
/** Get the corresponding adaptive icon drawable. */
public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
try (IconFactory iconFactory = IconFactory.obtain(context)) {
- final Bitmap iconBmp = iconFactory.createBadgedIconBitmap(icon, user,
- true /* shrinkNonAdaptiveIcons */).icon;
- return new BitmapDrawable(context.getResources(), iconBmp);
+ return iconFactory
+ .createBadgedIconBitmap(icon, new IconOptions().setUser(user))
+ .newIcon(context);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 1d8f71e..78ec58b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -18,11 +18,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.icu.text.ListFormatter;
+import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
@@ -155,9 +157,41 @@
return set;
}
- public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
+ /**
+ * Save the enabled/disabled input methods and selected subtype states into system settings.
+ *
+ * @param fragment The preference fragment user interact with.
+ * @param resolver The {@link ContentResolver} used to access the database.
+ * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+ * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+ */
+ public static void saveInputMethodSubtypeList(PreferenceFragmentCompat fragment,
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
boolean hasHardKeyboard) {
+ saveInputMethodSubtypeListForUserInternal(
+ fragment, resolver, inputMethodInfos, hasHardKeyboard, UserHandle.myUserId());
+ }
+
+ /**
+ * Save the enabled/disabled input methods and selected subtype states into system settings as
+ * given userId.
+ *
+ * @param fragment The preference fragment user interact with.
+ * @param resolver The {@link ContentResolver} used to access the database.
+ * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+ * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+ * @param userId The given userId
+ */
+ public static void saveInputMethodSubtypeListForUser(PreferenceFragmentCompat fragment,
+ ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+ boolean hasHardKeyboard, @UserIdInt int userId) {
+ saveInputMethodSubtypeListForUserInternal(
+ fragment, resolver, inputMethodInfos, hasHardKeyboard, userId);
+ }
+
+ private static void saveInputMethodSubtypeListForUserInternal(PreferenceFragmentCompat fragment,
+ ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+ boolean hasHardKeyboard, @UserIdInt int userId) {
String currentInputMethodId = Settings.Secure.getString(resolver,
Settings.Secure.DEFAULT_INPUT_METHOD);
final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
@@ -168,7 +202,7 @@
boolean needsToResetSelectedSubtype = false;
for (final InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId();
- final Preference pref = context.findPreference(imiId);
+ final Preference pref = fragment.findPreference(imiId);
if (pref == null) {
continue;
}
@@ -184,8 +218,11 @@
}
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
final boolean systemIme = imi.isSystem();
+ // Create context as given userId
+ final Context wrapperContext = userId == UserHandle.myUserId() ? fragment.getActivity()
+ : fragment.getActivity().createContextAsUser(UserHandle.of(userId), 0);
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
- context.getActivity()).isAlwaysCheckedIme(imi))
+ wrapperContext).isAlwaysCheckedIme(imi))
|| isImeChecked) {
if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
// imiId has just been enabled
@@ -198,7 +235,7 @@
for (int i = 0; i < subtypeCount; ++i) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
- final TwoStatePreference subtypePref = (TwoStatePreference) context
+ final TwoStatePreference subtypePref = (TwoStatePreference) fragment
.findPreference(imiId + subtypeHashCodeStr);
// In the Configure input method screen which does not have subtype preferences.
if (subtypePref == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 94a0c00..c1ab706 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -18,6 +18,7 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import android.annotation.UserIdInt;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -75,30 +76,34 @@
private final OnSavePreferenceListener mOnSaveListener;
private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
private final boolean mIsAllowedByOrganization;
+ @UserIdInt
+ private final int mUserId;
private AlertDialog mDialog = null;
/**
* A preference entry of an input method.
*
- * @param context The Context this is associated with.
+ * @param prefContext The Context this preference is associated with.
* @param imi The {@link InputMethodInfo} of this preference.
* @param isAllowedByOrganization false if the IME has been disabled by a device or profile
* owner.
* @param onSaveListener The listener called when this preference has been changed and needs
* to save the state to shared preference.
+ * @param userId The userId to specify the corresponding user for this preference.
*/
- public InputMethodPreference(final Context context, final InputMethodInfo imi,
- final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener) {
- this(context, imi, imi.loadLabel(context.getPackageManager()), isAllowedByOrganization,
- onSaveListener);
+ public InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
+ final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener,
+ final @UserIdInt int userId) {
+ this(prefContext, imi, imi.loadLabel(prefContext.getPackageManager()),
+ isAllowedByOrganization, onSaveListener, userId);
}
@VisibleForTesting
- InputMethodPreference(final Context context, final InputMethodInfo imi,
+ InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
final CharSequence title, final boolean isAllowedByOrganization,
- final OnSavePreferenceListener onSaveListener) {
- super(context);
+ final OnSavePreferenceListener onSaveListener, final @UserIdInt int userId) {
+ super(prefContext);
setPersistent(false);
mImi = imi;
mIsAllowedByOrganization = isAllowedByOrganization;
@@ -114,7 +119,12 @@
intent.setClassName(imi.getPackageName(), settingsActivity);
setIntent(intent);
}
- mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+ // Handle the context by given userId because {@link InputMethodSettingValuesWrapper} is
+ // per-user instance.
+ final Context userAwareContext = userId == UserHandle.myUserId() ? prefContext :
+ getContext().createContextAsUser(UserHandle.of(userId), 0);
+ mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(userAwareContext);
+ mUserId = userId;
mHasPriorityInSorting = imi.isSystem()
&& InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
setOnPreferenceClickListener(this);
@@ -130,17 +140,15 @@
super.onBindViewHolder(holder);
final Switch switchWidget = getSwitch();
if (switchWidget != null) {
+ // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
switchWidget.setOnClickListener(v -> {
- // no-op, avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}
- });
- switchWidget.setOnCheckedChangeListener((buttonView, isChecked) -> {
- // Avoid the invocation after we call {@link PrimarySwitchPreference#setChecked()}
- // in {@link setCheckedInternal}
- if (isChecked != isChecked()) {
- // Keep switch to previous state because we have to show the dialog first
- buttonView.setChecked(!isChecked);
- callChangeListener(isChecked());
+ if (!switchWidget.isEnabled()) {
+ return;
}
+ final boolean newValue = !isChecked();
+ // Keep switch to previous state because we have to show the dialog first.
+ switchWidget.setChecked(isChecked());
+ callChangeListener(newValue);
});
}
final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
@@ -187,7 +195,7 @@
final Intent intent = getIntent();
if (intent != null) {
// Invoke a settings activity of an input method.
- context.startActivity(intent);
+ context.startActivityAsUser(intent, UserHandle.of(mUserId));
}
} catch (final ActivityNotFoundException e) {
Log.d(TAG, "IME's Settings Activity Not Found", e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 13c1b82..39e6dce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -16,13 +16,18 @@
package com.android.settingslib.inputmethod;
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
import android.annotation.UiThread;
import android.content.ContentResolver;
import android.content.Context;
import android.util.Log;
+import android.util.SparseArray;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -39,20 +44,39 @@
public class InputMethodSettingValuesWrapper {
private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
- private static volatile InputMethodSettingValuesWrapper sInstance;
+ private static final Object sInstanceMapLock = new Object();
+ /**
+ * Manages mapping between user ID and corresponding singleton
+ * {@link InputMethodSettingValuesWrapper} object.
+ */
+ @GuardedBy("sInstanceMapLock")
+ private static SparseArray<InputMethodSettingValuesWrapper> sInstanceMap = new SparseArray<>();
private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
private final ContentResolver mContentResolver;
private final InputMethodManager mImm;
- public static InputMethodSettingValuesWrapper getInstance(Context context) {
- if (sInstance == null) {
- synchronized (TAG) {
- if (sInstance == null) {
- sInstance = new InputMethodSettingValuesWrapper(context);
- }
+ @AnyThread
+ @NonNull
+ public static InputMethodSettingValuesWrapper getInstance(@NonNull Context context) {
+ final int requestUserId = context.getUserId();
+ InputMethodSettingValuesWrapper valuesWrapper;
+ // First time to create the wrapper.
+ synchronized (sInstanceMapLock) {
+ if (sInstanceMap.size() == 0) {
+ valuesWrapper = new InputMethodSettingValuesWrapper(context);
+ sInstanceMap.put(requestUserId, valuesWrapper);
+ return valuesWrapper;
}
+ // We have same user context as request.
+ if (sInstanceMap.indexOfKey(requestUserId) >= 0) {
+ return sInstanceMap.get(requestUserId);
+ }
+ // Request by a new user context.
+ valuesWrapper = new InputMethodSettingValuesWrapper(context);
+ sInstanceMap.put(context.getUserId(), valuesWrapper);
}
- return sInstance;
+
+ return valuesWrapper;
}
// Ensure singleton
@@ -64,7 +88,7 @@
public void refreshAllInputMethodAndSubtypes() {
mMethodList.clear();
- mMethodList.addAll(mImm.getInputMethodList());
+ mMethodList.addAll(mImm.getInputMethodListAsUser(mContentResolver.getUserId()));
}
public List<InputMethodInfo> getInputMethodList() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
index 9962e1c..1e75014 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
@@ -20,6 +20,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -112,7 +113,8 @@
createInputMethodInfo(systemIme, name),
title,
true /* isAllowedByOrganization */,
- p -> {} /* onSavePreferenceListener */);
+ p -> {} /* onSavePreferenceListener */,
+ UserHandle.myUserId());
}
private static InputMethodInfo createInputMethodInfo(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a67b565..6072f68 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1067,14 +1067,17 @@
Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
GlobalSettingsProto.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE);
- final long nitzUpdateToken = p.start(GlobalSettingsProto.NITZ_UPDATE);
+ final long nitzToken = p.start(GlobalSettingsProto.NITZ);
dumpSetting(s, p,
Settings.Global.NITZ_UPDATE_DIFF,
- GlobalSettingsProto.NitzUpdate.DIFF);
+ GlobalSettingsProto.Nitz.UPDATE_DIFF);
dumpSetting(s, p,
Settings.Global.NITZ_UPDATE_SPACING,
- GlobalSettingsProto.NitzUpdate.SPACING);
- p.end(nitzUpdateToken);
+ GlobalSettingsProto.Nitz.UPDATE_SPACING);
+ dumpSetting(s, p,
+ Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
+ GlobalSettingsProto.Nitz.NETWORK_DISCONNECT_RETENTION);
+ p.end(nitzToken);
final long notificationToken = p.start(GlobalSettingsProto.NOTIFICATION);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c9c93c4..19ed021 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -384,6 +384,7 @@
Settings.Global.NETWORK_WATCHLIST_ENABLED,
Settings.Global.NEW_CONTACT_AGGREGATOR,
Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+ Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
Settings.Global.NITZ_UPDATE_DIFF,
Settings.Global.NITZ_UPDATE_SPACING,
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b311ee..867ab3c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -596,6 +596,9 @@
<!-- Permission required for CTS test - SettingsMultiPaneDeepLinkTest -->
<uses-permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK" />
+ <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
+ <uses-permission android:name="android.permission.LOCK_DEVICE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/drawable/media_ttt_chip_background.xml b/packages/SystemUI/res/drawable/media_ttt_chip_background.xml
new file mode 100644
index 0000000..3abf4d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_ttt_chip_background.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/colorSurface" />
+ <corners android:radius="32dp" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/media_ttt_undo_background.xml b/packages/SystemUI/res/drawable/media_ttt_undo_background.xml
new file mode 100644
index 0000000..ec74ee1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_ttt_undo_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ <corners android:radius="24dp" />
+</shape>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 4a5b637..e90a644 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -34,6 +34,7 @@
<TextView
android:id="@+id/internet_dialog_title"
+ android:ellipsize="end"
android:gravity="center_vertical|center_horizontal"
android:layout_width="wrap_content"
android:layout_height="32dp"
@@ -154,12 +155,18 @@
<TextView
android:id="@+id/mobile_summary"
style="@style/InternetDialog.NetworkSummary"/>
+ <TextView
+ android:id="@+id/airplane_mode_summary"
+ android:text="@string/airplane_mode"
+ android:visibility="gone"
+ style="@style/InternetDialog.NetworkSummary"/>
</LinearLayout>
<View
android:id="@+id/mobile_toggle_divider"
android:layout_width="1dp"
android:layout_height="28dp"
+ android:layout_marginStart="7dp"
android:layout_marginEnd="16dp"
android:layout_gravity="center_vertical"
android:background="?android:attr/textColorSecondary"/>
@@ -370,27 +377,60 @@
android:clickable="true"/>
</LinearLayout>
</LinearLayout>
-
<FrameLayout
- android:id="@+id/done_layout"
- android:layout_width="67dp"
+ android:id="@+id/button_layout"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
android:layout_height="48dp"
- android:layout_marginTop="8dp"
+ android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
+ android:layout_marginTop="8dp"
android:layout_marginBottom="34dp"
- android:layout_gravity="end|center_vertical"
- android:clickable="true"
- android:focusable="true">
- <Button
- android:text="@string/inline_done_button"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="match_parent"
- android:layout_height="36dp"
- android:layout_gravity="center"
- android:textAppearance="@style/TextAppearance.InternetDialog"
- android:textSize="14sp"
- android:background="@drawable/internet_dialog_footer_background"
- android:clickable="false"/>
+ android:clickable="false"
+ android:focusable="false">
+
+ <FrameLayout
+ android:id="@+id/apm_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_gravity="start|center_vertical"
+ android:orientation="vertical">
+ <Button
+ android:text="@string/turn_off_airplane_mode"
+ android:ellipsize="end"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:layout_gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"
+ android:background="@drawable/internet_dialog_footer_background"
+ android:clickable="false"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/done_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginStart="16dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_gravity="end|center_vertical"
+ android:orientation="vertical">
+ <Button
+ android:text="@string/inline_done_button"
+ android:ellipsize="end"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="67dp"
+ android:layout_height="36dp"
+ android:layout_gravity="end|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"
+ android:background="@drawable/internet_dialog_footer_background"
+ android:clickable="false"/>
+ </FrameLayout>
</FrameLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
new file mode 100644
index 0000000..6fbc41c
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -0,0 +1,64 @@
+<!--
+ ~ 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/media_ttt_chip_outer_padding"
+ android:background="@drawable/media_ttt_chip_background"
+ android:layout_marginTop="50dp"
+ android:clipToPadding="false"
+ android:gravity="center_vertical"
+ >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/media_ttt_text_size"
+ android:textColor="?android:attr/textColorPrimary"
+ />
+
+ <ProgressBar
+ android:id="@+id/loading"
+ android:indeterminate="true"
+ android:layout_width="@dimen/media_ttt_icon_size"
+ android:layout_height="@dimen/media_ttt_icon_size"
+ android:layout_marginStart="12dp"
+ android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
+ style="?android:attr/progressBarStyleSmall"
+ />
+
+ <TextView
+ android:id="@+id/undo"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/media_transfer_undo"
+ android:textColor="?androidprv:attr/textColorOnAccent"
+ android:layout_marginStart="12dp"
+ android:textSize="@dimen/media_ttt_text_size"
+ android:paddingStart="@dimen/media_ttt_chip_outer_padding"
+ android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
+ android:paddingTop="@dimen/media_ttt_undo_button_vertical_padding"
+ android:paddingBottom="@dimen/media_ttt_undo_button_vertical_padding"
+ android:layout_marginTop="@dimen/media_ttt_undo_button_vertical_negative_margin"
+ android:layout_marginBottom="@dimen/media_ttt_undo_button_vertical_negative_margin"
+ android:background="@drawable/media_ttt_undo_background"
+ />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 91b11fc..3a0df28 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -37,8 +37,8 @@
android:layout_height="@dimen/qs_framed_avatar_size"
android:layout_marginBottom="7dp"
systemui:frameWidth="6dp"
- systemui:badgeDiameter="18dp"
- systemui:badgeMargin="1dp"
+ systemui:badgeDiameter="15dp"
+ systemui:badgeMargin="5dp"
systemui:framePadding="-1dp"
systemui:frameColor="@color/qs_user_avatar_frame"/>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b3bbd87..ff748a9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -98,6 +98,7 @@
as custom(package/class). Relative class name is supported. -->
<string-array name="config_quickSettingsAutoAdd" translatable="false">
<item>accessibility_display_inversion_enabled:inversion</item>
+ <item>one_handed_mode_enabled:onehanded</item>
</string-array>
<!-- Show indicator for Wifi on but not connected. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a437ae6..0b56264 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -976,6 +976,13 @@
<dimen name="qs_aa_media_rec_album_margin_vert">4dp</dimen>
<dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
+ <!-- Media tap-to-transfer chip -->
+ <dimen name="media_ttt_chip_outer_padding">16dp</dimen>
+ <dimen name="media_ttt_text_size">16sp</dimen>
+ <dimen name="media_ttt_icon_size">16dp</dimen>
+ <dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
+ <dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
+
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
<dimen name="magnification_outer_border_margin">15dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 300cb2d3..43254aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2164,6 +2164,15 @@
<!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
<string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
+ <!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_button_play">Play</string>
+ <!-- Description for button in media controls. Pressing button pauses playback [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_button_pause">Pause</string>
+ <!-- Description for button in media controls. Pressing button goes to previous track [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_button_prev">Previous track</string>
+ <!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_button_next">Next track</string>
+
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
<!-- Description for Smartspace recommendation card within media controls [CHAR_LIMIT=NONE] -->
@@ -2173,6 +2182,14 @@
<!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
<string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
+ <!--- ****** Media tap-to-transfer ****** -->
+ <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
+ <string name="media_transfer_undo">Undo</string>
+ <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play music on the different device. [CHAR LIMIT=75] -->
+ <string name="media_move_closer_to_transfer">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+ <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
+ <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
<!-- Error message indicating that the control is no longer available in the application [CHAR LIMIT=30] -->
@@ -2347,6 +2364,8 @@
<string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
<!-- Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi scanning is on. [CHAR LIMIT=NONE] -->
<string name="wifi_scan_notify_message">To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. You can change this in Wi\u2011Fi scanning settings. <annotation id="link">Change</annotation></string>
+ <!-- Provider Model: Description of the airplane mode button. [CHAR LIMIT=60] -->
+ <string name="turn_off_airplane_mode">Turn off airplane mode</string>
<!-- Text for TileService request dialog. This is shown to the user that an app is requesting
user approval to add the shown tile to Quick Settings [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 9574101..b611c96 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -16,28 +16,31 @@
package com.android.systemui.flags
+import android.annotation.BoolRes
+import android.annotation.IntegerRes
+import android.annotation.StringRes
import android.os.Parcel
import android.os.Parcelable
-interface Flag<T> : Parcelable {
+interface Flag<T> {
val id: Int
+}
+
+interface ParcelableFlag<T> : Flag<T>, Parcelable {
val default: T
- val resourceOverride: Int
-
override fun describeContents() = 0
+}
- fun hasResourceOverride(): Boolean {
- return resourceOverride != -1
- }
+interface ResourceFlag<T> : Flag<T> {
+ val resourceId: Int
}
// Consider using the "parcelize" kotlin library.
data class BooleanFlag @JvmOverloads constructor(
override val id: Int,
- override val default: Boolean = false,
- override val resourceOverride: Int = -1
-) : Flag<Boolean> {
+ override val default: Boolean = false
+) : ParcelableFlag<Boolean> {
companion object {
@JvmField
@@ -58,11 +61,15 @@
}
}
+data class ResourceBooleanFlag constructor(
+ override val id: Int,
+ @BoolRes override val resourceId: Int
+) : ResourceFlag<Boolean>
+
data class StringFlag @JvmOverloads constructor(
override val id: Int,
- override val default: String = "",
- override val resourceOverride: Int = -1
-) : Flag<String> {
+ override val default: String = ""
+) : ParcelableFlag<String> {
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<StringFlag> {
@@ -82,11 +89,15 @@
}
}
+data class ResourceStringFlag constructor(
+ override val id: Int,
+ @StringRes override val resourceId: Int
+) : ResourceFlag<String>
+
data class IntFlag @JvmOverloads constructor(
override val id: Int,
- override val default: Int = 0,
- override val resourceOverride: Int = -1
-) : Flag<Int> {
+ override val default: Int = 0
+) : ParcelableFlag<Int> {
companion object {
@JvmField
@@ -107,11 +118,15 @@
}
}
+data class ResourceIntFlag constructor(
+ override val id: Int,
+ @IntegerRes override val resourceId: Int
+) : ResourceFlag<Int>
+
data class LongFlag @JvmOverloads constructor(
override val id: Int,
- override val default: Long = 0,
- override val resourceOverride: Int = -1
-) : Flag<Long> {
+ override val default: Long = 0
+) : ParcelableFlag<Long> {
companion object {
@JvmField
@@ -134,9 +149,8 @@
data class FloatFlag @JvmOverloads constructor(
override val id: Int,
- override val default: Float = 0f,
- override val resourceOverride: Int = -1
-) : Flag<Float> {
+ override val default: Float = 0f
+) : ParcelableFlag<Float> {
companion object {
@JvmField
@@ -157,11 +171,15 @@
}
}
+data class ResourceFloatFlag constructor(
+ override val id: Int,
+ override val resourceId: Int
+) : ResourceFlag<Int>
+
data class DoubleFlag @JvmOverloads constructor(
override val id: Int,
- override val default: Double = 0.0,
- override val resourceOverride: Int = -1
-) : Flag<Double> {
+ override val default: Double = 0.0
+) : ParcelableFlag<Double> {
companion object {
@JvmField
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index e61cb5c..b2ca2d7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -60,7 +60,7 @@
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras: Bundle? = getResultExtras(false)
- val listOfFlags: java.util.ArrayList<Flag<*>>? =
+ val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
extras?.getParcelableArrayList(FIELD_FLAGS)
if (listOfFlags != null) {
completer.set(listOfFlags)
@@ -108,6 +108,10 @@
}
}
+ override fun isEnabled(flag: ResourceBooleanFlag): Boolean {
+ throw RuntimeException("Not implemented in FlagManager")
+ }
+
override fun addListener(listener: FlagReader.Listener) {
synchronized(listeners) {
val registerNeeded = listeners.isEmpty()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
index 91a3912..26c6a4b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
@@ -24,6 +24,8 @@
return flag.default
}
+ fun isEnabled(flag: ResourceBooleanFlag): Boolean
+
/** Returns a boolean value for the given flag. */
fun isEnabled(id: Int, def: Boolean): Boolean {
return def
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index a319b40..3cd090e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -270,7 +270,7 @@
mOpeningLeashes.add(openingTasks.get(i).getLeash());
// We are receiving new opening tasks, so convert to onTasksAppeared.
final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
- openingTasks.get(i), layer, mInfo, t);
+ openingTasks.get(i), layer, info, t);
mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
t.setLayer(target.leash.mSurfaceControl, layer);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index b6be6ed..e46b6f1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -26,6 +26,7 @@
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -62,7 +63,7 @@
mainExecutor
)
- return if (config.isHingeAngleEnabled) {
+ val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
PhysicsBasedUnfoldTransitionProgressProvider(
mainHandler,
foldStateProvider
@@ -70,6 +71,10 @@
} else {
FixedTimingTransitionProgressProvider(foldStateProvider)
}
+ return ScaleAwareTransitionProgressProvider(
+ unfoldTransitionProgressProvider,
+ context.contentResolver
+ )
}
fun createConfig(context: Context): UnfoldTransitionConfig =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index e072d41..58d7dfb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -2,7 +2,6 @@
import android.content.Context
import android.os.RemoteException
-import android.util.Log
import android.view.IRotationWatcher
import android.view.IWindowManager
import android.view.Surface
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
new file mode 100644
index 0000000..df9078a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */
+class ScaleAwareTransitionProgressProvider(
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val contentResolver: ContentResolver
+) : UnfoldTransitionProgressProvider {
+
+ private val scopedUnfoldTransitionProgressProvider =
+ ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+
+ private val animatorDurationScaleObserver = object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ onAnimatorScaleChanged()
+ }
+ }
+
+ init {
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ /* notifyForDescendants= */ false,
+ animatorDurationScaleObserver)
+ onAnimatorScaleChanged()
+ }
+
+ private fun onAnimatorScaleChanged() {
+ val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
+ scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ scopedUnfoldTransitionProgressProvider.addCallback(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ scopedUnfoldTransitionProgressProvider.removeCallback(listener)
+ }
+
+ override fun destroy() {
+ contentResolver.unregisterContentObserver(animatorDurationScaleObserver)
+ scopedUnfoldTransitionProgressProvider.destroy()
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
deleted file mode 100644
index 543232d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.unfold.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages progress listeners that can have smaller lifespan than the unfold animation.
- * Allows to limit getting transition updates to only when
- * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
- * with readyToHandleTransition = true
- *
- * If the transition has already started by the moment when the clients are ready to play
- * the transition then it will report transition started callback and current animation progress.
- */
-public final class ScopedUnfoldTransitionProgressProvider implements
- UnfoldTransitionProgressProvider, TransitionProgressListener {
-
- private static final float PROGRESS_UNSET = -1f;
-
- @Nullable
- private UnfoldTransitionProgressProvider mSource;
-
- private final List<TransitionProgressListener> mListeners = new ArrayList<>();
-
- private boolean mIsReadyToHandleTransition;
- private boolean mIsTransitionRunning;
- private float mLastTransitionProgress = PROGRESS_UNSET;
-
- public ScopedUnfoldTransitionProgressProvider() {
- this(null);
- }
-
- public ScopedUnfoldTransitionProgressProvider(
- @Nullable UnfoldTransitionProgressProvider source) {
- setSourceProvider(source);
- }
-
- /**
- * Sets the source for the unfold transition progress updates,
- * it replaces current provider if it is already set
- * @param provider transition provider that emits transition progress updates
- */
- public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
- if (mSource != null) {
- mSource.removeCallback(this);
- }
-
- if (provider != null) {
- mSource = provider;
- mSource.addCallback(this);
- } else {
- mSource = null;
- }
- }
-
- /**
- * Allows to notify this provide whether the listeners can play the transition or not.
- * Call this method with readyToHandleTransition = true when all listeners
- * are ready to consume the transition progress events.
- * Call it with readyToHandleTransition = false when listeners can't process the events.
- */
- public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
- if (mIsTransitionRunning) {
- if (isReadyToHandleTransition) {
- mListeners.forEach(TransitionProgressListener::onTransitionStarted);
-
- if (mLastTransitionProgress != PROGRESS_UNSET) {
- mListeners.forEach(listener ->
- listener.onTransitionProgress(mLastTransitionProgress));
- }
- } else {
- mIsTransitionRunning = false;
- mListeners.forEach(TransitionProgressListener::onTransitionFinished);
- }
- }
-
- mIsReadyToHandleTransition = isReadyToHandleTransition;
- }
-
- @Override
- public void addCallback(@NonNull TransitionProgressListener listener) {
- mListeners.add(listener);
- }
-
- @Override
- public void removeCallback(@NonNull TransitionProgressListener listener) {
- mListeners.remove(listener);
- }
-
- @Override
- public void destroy() {
- mSource.removeCallback(this);
- }
-
- @Override
- public void onTransitionStarted() {
- this.mIsTransitionRunning = true;
- if (mIsReadyToHandleTransition) {
- mListeners.forEach(TransitionProgressListener::onTransitionStarted);
- }
- }
-
- @Override
- public void onTransitionProgress(float progress) {
- if (mIsReadyToHandleTransition) {
- mListeners.forEach(listener -> listener.onTransitionProgress(progress));
- }
-
- mLastTransitionProgress = progress;
- }
-
- @Override
- public void onTransitionFinished() {
- if (mIsReadyToHandleTransition) {
- mListeners.forEach(TransitionProgressListener::onTransitionFinished);
- }
-
- mIsTransitionRunning = false;
- mLastTransitionProgress = PROGRESS_UNSET;
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
new file mode 100644
index 0000000..22698a8
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.systemui.unfold.util
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor(
+ source: UnfoldTransitionProgressProvider? = null
+) : UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private var source: UnfoldTransitionProgressProvider? = null
+
+ private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+
+ private var isReadyToHandleTransition = false
+ private var isTransitionRunning = false
+ private var lastTransitionProgress = PROGRESS_UNSET
+
+ init {
+ setSourceProvider(source)
+ }
+ /**
+ * Sets the source for the unfold transition progress updates,
+ * it replaces current provider if it is already set
+ * @param provider transition provider that emits transition progress updates
+ */
+ fun setSourceProvider(provider: UnfoldTransitionProgressProvider?) {
+ source?.removeCallback(this)
+
+ if (provider != null) {
+ source = provider
+ provider.addCallback(this)
+ } else {
+ source = null
+ }
+ }
+
+ /**
+ * Allows to notify this provide whether the listeners can play the transition or not.
+ * Call this method with readyToHandleTransition = true when all listeners
+ * are ready to consume the transition progress events.
+ * Call it with readyToHandleTransition = false when listeners can't process the events.
+ */
+ fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
+ if (isTransitionRunning) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ if (lastTransitionProgress != PROGRESS_UNSET) {
+ listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+ }
+ } else {
+ isTransitionRunning = false
+ listeners.forEach { it.onTransitionFinished() }
+ }
+ }
+ this.isReadyToHandleTransition = isReadyToHandleTransition
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners += listener
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners -= listener
+ }
+
+ override fun destroy() {
+ source?.removeCallback(this)
+ }
+
+ override fun onTransitionStarted() {
+ isTransitionRunning = true
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ }
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+ lastTransitionProgress = progress
+ }
+
+ override fun onTransitionFinished() {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinished() }
+ }
+ isTransitionRunning = false
+ lastTransitionProgress = PROGRESS_UNSET
+ }
+
+ companion object {
+ private const val PROGRESS_UNSET = -1f
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index 3ebfb51..9b72655 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -22,12 +22,11 @@
import com.android.systemui.communal.conditions.CommunalCondition;
import com.android.systemui.communal.conditions.CommunalSettingCondition;
-import com.android.systemui.communal.conditions.CommunalTrustedNetworkCondition;
import com.android.systemui.idle.AmbientLightModeMonitor;
import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
import com.android.systemui.idle.dagger.IdleViewComponent;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -73,9 +72,7 @@
@ElementsIntoSet
@Named(COMMUNAL_CONDITIONS)
static Set<CommunalCondition> provideCommunalConditions(
- CommunalSettingCondition communalSettingCondition,
- CommunalTrustedNetworkCondition communalTrustedNetworkCondition) {
- return new HashSet<>(
- Arrays.asList(communalSettingCondition, communalTrustedNetworkCondition));
+ CommunalSettingCondition communalSettingCondition) {
+ return new HashSet<>(Collections.singletonList(communalSettingCondition));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1d17fd8..c4a58db 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -112,6 +113,7 @@
LogModule.class,
PeopleHubModule.class,
PluginModule.class,
+ QsFrameTranslateModule.class,
ScreenshotModule.class,
SensorModule.class,
SettingsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 0ee47a7..3f00b87 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -22,6 +22,7 @@
import static com.android.systemui.flags.FlagManager.FIELD_ID;
import static com.android.systemui.flags.FlagManager.FIELD_VALUE;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -30,7 +31,6 @@
import android.os.Bundle;
import android.util.Log;
-import androidx.annotation.BoolRes;
import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
@@ -89,16 +89,18 @@
public boolean isEnabled(BooleanFlag flag) {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
- boolean def = flag.getDefault();
- if (flag.hasResourceOverride()) {
- try {
- def = isEnabledInOverlay(flag.getResourceOverride());
- } catch (Resources.NotFoundException e) {
- // no-op
- }
- }
+ mBooleanFlagCache.put(id, isEnabled(id, flag.getDefault()));
+ }
- mBooleanFlagCache.put(id, isEnabled(id, def));
+ return mBooleanFlagCache.get(id);
+ }
+
+ @Override
+ public boolean isEnabled(ResourceBooleanFlag flag) {
+ int id = flag.getId();
+ if (!mBooleanFlagCache.containsKey(id)) {
+ mBooleanFlagCache.put(
+ id, isEnabled(id, mResources.getBoolean(flag.getResourceId())));
}
return mBooleanFlagCache.get(id);
@@ -111,6 +113,7 @@
return result == null ? defaultValue : result;
}
+
/** Returns the stored value or null if not set. */
private Boolean isEnabledInternal(int id) {
try {
@@ -121,10 +124,6 @@
return null;
}
- private boolean isEnabledInOverlay(@BoolRes int resId) {
- return mResources.getBoolean(resId);
- }
-
/** Set whether a given {@link BooleanFlag} is enabled or not. */
public void setEnabled(int id, boolean value) {
Boolean currentValue = isEnabledInternal(id);
@@ -185,9 +184,19 @@
} else if (ACTION_GET_FLAGS.equals(action)) {
Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
+
+ // Convert all flags to parcelable flags.
+ ArrayList<ParcelableFlag<?>> pFlags = new ArrayList<>();
+ for (Flag<?> f : flags) {
+ ParcelableFlag<?> pf = toParcelableFlag(f);
+ if (pf != null) {
+ pFlags.add(pf);
+ }
+ }
+
Bundle extras = getResultExtras(true);
if (extras != null) {
- extras.putParcelableArrayList(FIELD_FLAGS, flags);
+ extras.putParcelableArrayList(FIELD_FLAGS, pFlags);
}
}
}
@@ -215,6 +224,25 @@
setEnabled(id, extras.getBoolean(FIELD_VALUE));
}
}
+
+ /**
+ * Ensures that the data we send to the app reflects the current state of the flags.
+ *
+ * Also converts an non-parcelable versions of the flags to their parcelable versions.
+ */
+ @Nullable
+ private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
+ if (f instanceof BooleanFlag) {
+ return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f));
+ }
+ if (f instanceof ResourceBooleanFlag) {
+ return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
+ }
+
+ // TODO: add support for other flag types.
+ Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
+ return null;
+ }
};
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index bd6cb66..5b6404f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,12 +16,14 @@
package com.android.systemui.flags;
+import android.content.res.Resources;
import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
@@ -37,9 +39,11 @@
*/
@SysUISingleton
public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
- SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
+ private final Resources mResources;
+ SparseBooleanArray mFlagCache = new SparseBooleanArray();
@Inject
- public FeatureFlagsRelease(DumpManager dumpManager) {
+ public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
+ mResources = resources;
dumpManager.registerDumpable("SysUIFlags", this);
}
@@ -55,18 +59,28 @@
}
@Override
+ public boolean isEnabled(ResourceBooleanFlag flag) {
+ int cacheIndex = mFlagCache.indexOfKey(flag.getId());
+ if (cacheIndex < 0) {
+ return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+ }
+
+ return mFlagCache.valueAt(cacheIndex);
+ }
+
+ @Override
public boolean isEnabled(int key, boolean defaultValue) {
- mAccessedFlags.append(key, defaultValue);
+ mFlagCache.append(key, defaultValue);
return defaultValue;
}
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: false");
- int size = mAccessedFlags.size();
+ int size = mFlagCache.size();
for (int i = 0; i < size; i++) {
- pw.println(" sysui_flag_" + mAccessedFlags.keyAt(i)
- + ": " + mAccessedFlags.valueAt(i));
+ pw.println(" sysui_flag_" + mFlagCache.keyAt(i)
+ + ": " + mFlagCache.valueAt(i));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 458cdc1f..97533c9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -61,8 +61,8 @@
public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
new BooleanFlag(202, true);
- public static final BooleanFlag CHARGING_RIPPLE =
- new BooleanFlag(203, false, R.bool.flag_charging_ripple);
+ public static final ResourceBooleanFlag CHARGING_RIPPLE =
+ new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
/***************************************/
// 300 - power menu
@@ -77,8 +77,8 @@
public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
new BooleanFlag(401, false);
- public static final BooleanFlag SMARTSPACE =
- new BooleanFlag(402, false, R.bool.flag_smartspace);
+ public static final ResourceBooleanFlag SMARTSPACE =
+ new ResourceBooleanFlag(402, R.bool.flag_smartspace);
/***************************************/
// 500 - quick settings
@@ -88,11 +88,11 @@
public static final BooleanFlag COMBINED_QS_HEADERS =
new BooleanFlag(501, false);
- public static final BooleanFlag PEOPLE_TILE =
- new BooleanFlag(502, false, R.bool.flag_conversations);
+ public static final ResourceBooleanFlag PEOPLE_TILE =
+ new ResourceBooleanFlag(502, R.bool.flag_conversations);
- public static final BooleanFlag QS_USER_DETAIL_SHORTCUT =
- new BooleanFlag(503, false, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+ public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
+ new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
/***************************************/
// 600- status bar
@@ -115,12 +115,13 @@
/***************************************/
// 800 - general visual/theme
- public static final BooleanFlag MONET =
- new BooleanFlag(800, true, R.bool.flag_monet);
+ public static final ResourceBooleanFlag MONET =
+ new ResourceBooleanFlag(800, R.bool.flag_monet);
/***************************************/
// 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+ public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b85f1072..e921ad29c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -862,8 +862,23 @@
@VisibleForTesting
internal object MediaPlayerData {
- private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
- emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+ private val EMPTY = MediaData(
+ userId = -1,
+ initialized = false,
+ backgroundColor = 0,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "INVALID",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null)
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f66eb5b..63555bb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -62,6 +62,7 @@
import com.android.systemui.util.time.SystemClock;
import java.net.URISyntaxException;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -122,6 +123,8 @@
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private final FalsingManager mFalsingManager;
+ private final MediaFlags mMediaFlags;
+
// Used for swipe-to-dismiss logging.
protected boolean mIsImpressed = false;
private SystemClock mSystemClock;
@@ -138,7 +141,7 @@
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
- FalsingManager falsingManager, SystemClock systemClock) {
+ FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -149,8 +152,8 @@
mMediaOutputDialogFactory = mediaOutputDialogFactory;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
+ mMediaFlags = mediaFlags;
mSystemClock = systemClock;
-
loadDimens();
mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -426,33 +429,61 @@
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
- List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
- // Media controls
- int i = 0;
+ // Media action buttons
List<MediaAction> actionIcons = data.getActions();
+ List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+
+ if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
+ // Use PlaybackState actions instead
+ MediaButton semanticActions = data.getSemanticActions();
+
+ actionIcons = new ArrayList<MediaAction>();
+ actionIcons.add(semanticActions.getStartCustom());
+ actionIcons.add(semanticActions.getPrevOrCustom());
+ actionIcons.add(semanticActions.getPlayOrPause());
+ actionIcons.add(semanticActions.getNextOrCustom());
+ actionIcons.add(semanticActions.getEndCustom());
+
+ actionsWhenCollapsed = new ArrayList<Integer>();
+ actionsWhenCollapsed.add(1);
+ actionsWhenCollapsed.add(2);
+ actionsWhenCollapsed.add(3);
+ }
+
+ int i = 0;
for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
int actionId = ACTION_IDS[i];
+ boolean visibleInCompat = actionsWhenCollapsed.contains(i);
final ImageButton button = mPlayerViewHolder.getAction(actionId);
MediaAction mediaAction = actionIcons.get(i);
- button.setImageIcon(mediaAction.getIcon());
- button.setContentDescription(mediaAction.getContentDescription());
- Runnable action = mediaAction.getAction();
+ if (mediaAction != null) {
+ button.setImageIcon(mediaAction.getIcon());
+ button.setContentDescription(mediaAction.getContentDescription());
+ Runnable action = mediaAction.getAction();
- if (action == null) {
- button.setEnabled(false);
+ if (action == null) {
+ button.setEnabled(false);
+ } else {
+ button.setEnabled(true);
+ button.setOnClickListener(v -> {
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+ /* isRecommendationCard */ false);
+ action.run();
+ }
+ });
+ }
+ setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+ setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
} else {
- button.setEnabled(true);
- button.setOnClickListener(v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
- /* isRecommendationCard */ false);
- action.run();
- }
- });
+ button.setImageIcon(null);
+ button.setContentDescription(null);
+ button.setEnabled(false);
+ setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+ // for expanded layout, set as INVISIBLE so that we still reserve space in the UI
+ expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
+ expandedSet.setAlpha(actionId, 0.0f);
}
- boolean visibleInCompat = actionsWhenCollapsed.contains(i);
- setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
- setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
}
// Hide any unused buttons
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index bda07f4..4b8dfde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -47,7 +47,7 @@
*/
val artwork: Icon?,
/**
- * List of actions that can be performed on the player: prev, next, play, pause, etc.
+ * List of generic action buttons for the media player, based on notification actions
*/
val actions: List<MediaAction>,
/**
@@ -55,6 +55,11 @@
*/
val actionsToShowInCompact: List<Int>,
/**
+ * Semantic actions buttons, based on the PlaybackState of the media session.
+ * If present, these actions will be preferred in the UI over [actions]
+ */
+ val semanticActions: MediaButton? = null,
+ /**
* Package name of the app that's posting the media.
*/
val packageName: String,
@@ -125,6 +130,32 @@
}
}
+/**
+ * Contains [MediaAction] objects which represent specific buttons in the UI
+ */
+data class MediaButton(
+ /**
+ * Play/pause button
+ */
+ var playOrPause: MediaAction? = null,
+ /**
+ * Next button, or custom action
+ */
+ var nextOrCustom: MediaAction? = null,
+ /**
+ * Previous button, or custom action
+ */
+ var prevOrCustom: MediaAction? = null,
+ /**
+ * First custom action space
+ */
+ var startCustom: MediaAction? = null,
+ /**
+ * Last custom action space
+ */
+ var endCustom: MediaAction? = null
+)
+
/** State of a media action. */
data class MediaAction(
val icon: Icon?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 7c0f7fc..49a63c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -38,6 +38,7 @@
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
+import android.media.session.PlaybackState
import android.net.Uri
import android.os.Parcelable
import android.os.UserHandle
@@ -45,6 +46,7 @@
import android.service.notification.StatusBarNotification
import android.text.TextUtils
import android.util.Log
+import androidx.media.utils.MediaConstants
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -80,8 +82,24 @@
private const val DEBUG = true
private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
-private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
- emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+private val LOADING = MediaData(
+ userId = -1,
+ initialized = false,
+ backgroundColor = 0,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "INVALID",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null)
+
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
"INVALID", null, emptyList(), null, 0, 0)
@@ -112,7 +130,8 @@
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val tunerService: TunerService
+ private val tunerService: TunerService,
+ private val mediaFlags: MediaFlags,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -127,6 +146,10 @@
// Maximum number of actions allowed in compact view
@JvmField
val MAX_COMPACT_ACTIONS = 3
+
+ /** Maximum number of [PlaybackState.CustomAction] buttons supported */
+ @JvmField
+ val MAX_CUSTOM_ACTIONS = 4
}
private val themeText = com.android.settingslib.Utils.getColorAttr(context,
@@ -182,12 +205,13 @@
activityStarter: ActivityStarter,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- tunerService: TunerService
+ tunerService: TunerService,
+ mediaFlags: MediaFlags
) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
- Utils.useQsMediaPlayer(context), clock, tunerService)
+ Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags)
private val appChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -522,7 +546,7 @@
foregroundExecutor.execute {
onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
- packageName, token, appIntent, device = null, active = false,
+ null, packageName, token, appIntent, device = null, active = false,
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
hasCheckedForResume = true, lastActive = lastActive))
}
@@ -594,15 +618,55 @@
}
// Control buttons
+ // If flag is enabled and controller has a PlaybackState, create actions from session info
+ // Otherwise, use the notification actions
+ var actionIcons: List<MediaAction> = emptyList()
+ var actionsToShowCollapsed: List<Int> = emptyList()
+ var semanticActions: MediaButton? = null
+ if (mediaFlags.areMediaSessionActionsEnabled() && mediaController.playbackState != null) {
+ semanticActions = createActionsFromState(sbn.packageName, mediaController)
+ } else {
+ val actions = createActionsFromNotification(sbn)
+ actionIcons = actions.first
+ actionsToShowCollapsed = actions.second
+ }
+
+ val playbackLocation =
+ if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
+ else if (mediaController.playbackInfo?.playbackType ==
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
+ else MediaData.PLAYBACK_CAST_LOCAL
+ val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
+ val lastActive = systemClock.elapsedRealtime()
+ foregroundExecutor.execute {
+ val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
+ val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+ val active = mediaEntries[key]?.active ?: true
+ onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
+ smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
+ semanticActions, sbn.packageName, token, notif.contentIntent, null,
+ active, resumeAction = resumeAction, playbackLocation = playbackLocation,
+ notificationKey = key, hasCheckedForResume = hasCheckedForResume,
+ isPlaying = isPlaying, isClearable = sbn.isClearable(),
+ lastActive = lastActive))
+ }
+ }
+
+ /**
+ * Generate action buttons based on notification actions
+ */
+ private fun createActionsFromNotification(sbn: StatusBarNotification):
+ Pair<List<MediaAction>, List<Int>> {
+ val notif = sbn.notification
val actionIcons: MutableList<MediaAction> = ArrayList()
val actions = notif.actions
var actionsToShowCollapsed = notif.extras.getIntArray(
- Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf<Int>()
+ Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf()
if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
- Log.e(TAG, "Too many compact actions for $key, limiting to first $MAX_COMPACT_ACTIONS")
+ Log.e(TAG, "Too many compact actions for ${sbn.key}," +
+ "limiting to first $MAX_COMPACT_ACTIONS")
actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
}
- // TODO: b/153736623 look into creating actions when this isn't a media style notification
if (actions != null) {
for ((index, action) in actions.withIndex()) {
@@ -631,32 +695,150 @@
action.getIcon()
}.setTint(themeText)
val mediaAction = MediaAction(
- mediaActionIcon,
- runnable,
- action.title)
+ mediaActionIcon,
+ runnable,
+ action.title)
actionIcons.add(mediaAction)
}
}
+ return Pair(actionIcons, actionsToShowCollapsed)
+ }
- val playbackLocation =
- if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
- else if (mediaController.playbackInfo?.playbackType ==
- MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
- else MediaData.PLAYBACK_CAST_LOCAL
- val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
- val lastActive = systemClock.elapsedRealtime()
- foregroundExecutor.execute {
- val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
- val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
- val active = mediaEntries[key]?.active ?: true
- onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
- smallIcon, artist, song, artWorkIcon, actionIcons,
- actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
- active, resumeAction = resumeAction, playbackLocation = playbackLocation,
- notificationKey = key, hasCheckedForResume = hasCheckedForResume,
- isPlaying = isPlaying, isClearable = sbn.isClearable(),
- lastActive = lastActive))
+ /**
+ * Generates action button info for this media session based on the PlaybackState
+ *
+ * @param packageName Package name for the media app
+ * @param controller MediaController for the current session
+ * @return a Pair consisting of a list of media actions, and a list of ints representing which
+ * of those actions should be shown in the compact player
+ */
+ private fun createActionsFromState(packageName: String, controller: MediaController):
+ MediaButton? {
+ val actions = MediaButton()
+ controller.playbackState?.let { state ->
+ // First, check for standard actions
+ actions.playOrPause = if (isPlayingState(state.state)) {
+ getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
+ } else {
+ getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
+ }
+ val prevButton = getStandardAction(controller, state.actions,
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+ val nextButton = getStandardAction(controller, state.actions,
+ PlaybackState.ACTION_SKIP_TO_NEXT)
+
+ // Then, check for custom actions
+ val customActions = MutableList<MediaAction?>(4) { null }
+ var customCount = 0
+ for (i in 0..MAX_CUSTOM_ACTIONS) {
+ getCustomAction(state, packageName, controller, customCount)?.let {
+ customActions[customCount++] = it
+ }
+ }
+
+ // Finally, assign the remaining button slots: C A play/pause B D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev = controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true
+ val reserveNext = controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true
+ var customIdx = 0
+
+ actions.prevOrCustom = if (prevButton != null) {
+ prevButton
+ } else if (!reservePrev) {
+ customActions[customIdx++]
+ } else {
+ null
+ }
+
+ actions.nextOrCustom = if (nextButton != null) {
+ nextButton
+ } else if (!reserveNext) {
+ customActions[customIdx++]
+ } else {
+ null
+ }
+
+ actions.startCustom = customActions[customIdx++]
+ actions.endCustom = customActions[customIdx++]
}
+ return actions
+ }
+
+ /**
+ * Get a [MediaAction] representing one of
+ * - [PlaybackState.ACTION_PLAY]
+ * - [PlaybackState.ACTION_PAUSE]
+ * - [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
+ * - [PlaybackState.ACTION_SKIP_TO_NEXT]
+ */
+ private fun getStandardAction(
+ controller: MediaController,
+ stateActions: Long,
+ action: Long
+ ): MediaAction? {
+ if (stateActions and action == 0L) {
+ return null
+ }
+
+ return when (action) {
+ PlaybackState.ACTION_PLAY -> {
+ MediaAction(
+ Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+ { controller.transportControls.play() },
+ context.getString(R.string.controls_media_button_play)
+ )
+ }
+ PlaybackState.ACTION_PAUSE -> {
+ MediaAction(
+ Icon.createWithResource(context,
+ com.android.internal.R.drawable.ic_media_pause),
+ { controller.transportControls.pause() },
+ context.getString(R.string.controls_media_button_pause)
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+ MediaAction(
+ Icon.createWithResource(context,
+ com.android.internal.R.drawable.ic_media_previous),
+ { controller.transportControls.skipToPrevious() },
+ context.getString(R.string.controls_media_button_prev)
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_NEXT -> {
+ MediaAction(
+ Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+ { controller.transportControls.skipToNext() },
+ context.getString(R.string.controls_media_button_next)
+ )
+ }
+ else -> null
+ }
+ }
+
+ /**
+ * Get a [MediaAction] representing a [PlaybackState.CustomAction]
+ */
+ private fun getCustomAction(
+ state: PlaybackState,
+ packageName: String,
+ controller: MediaController,
+ index: Int
+ ): MediaAction? {
+ if (state.customActions.size <= index || state.customActions[index] == null) {
+ if (DEBUG) { Log.d(TAG, "not enough actions or action was null at $index") }
+ return null
+ }
+
+ val it = state.customActions[index]
+ return MediaAction(
+ Icon.createWithResource(packageName, it.icon),
+ { controller.transportControls.sendCustomAction(it, it.extras) },
+ it.name
+ )
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
new file mode 100644
index 0000000..b4a4b42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+ /**
+ * Check whether media control actions should be based on PlaybackState instead of notification
+ */
+ fun areMediaSessionActionsEnabled(): Boolean {
+ return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 237d077..cf679f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -83,6 +83,6 @@
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
- return Optional.of(new MediaTttChipController(context, commandRegistry, windowManager));
+ return Optional.of(new MediaTttChipController(commandRegistry, context, windowManager));
}
}
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 85e5b33..af8fc0e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
@@ -19,9 +19,14 @@
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
import android.view.WindowManager
+import android.widget.LinearLayout
import android.widget.TextView
+import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -36,8 +41,8 @@
*/
@SysUISingleton
class MediaTttChipController @Inject constructor(
- context: Context,
commandRegistry: CommandRegistry,
+ private val context: Context,
private val windowManager: WindowManager,
) {
init {
@@ -46,9 +51,9 @@
}
private val windowLayoutParams = WindowManager.LayoutParams().apply {
- width = WindowManager.LayoutParams.MATCH_PARENT
- height = WindowManager.LayoutParams.MATCH_PARENT
- gravity = Gravity.CENTER_HORIZONTAL
+ width = WindowManager.LayoutParams.WRAP_CONTENT
+ height = WindowManager.LayoutParams.WRAP_CONTENT
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
title = "Media Tap-To-Transfer Chip View"
flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -57,29 +62,71 @@
setTrustedOverlay()
}
- // TODO(b/203800327): Create a layout that matches UX.
- private val chipView: TextView = TextView(context).apply {
- text = "Media Tap-To-Transfer Chip"
- }
+ /** The chip view currently being displayed. Null if the chip is not being displayed. */
+ private var chipView: LinearLayout? = null
- private var chipDisplaying: Boolean = false
+ private fun displayChip(chipType: ChipType, otherDeviceName: String) {
+ val oldChipView = chipView
+ if (chipView == null) {
+ chipView = LayoutInflater
+ .from(context)
+ .inflate(R.layout.media_ttt_chip, null) as LinearLayout
+ }
+ val currentChipView = chipView!!
- private fun addChip() {
- if (chipDisplaying) { return }
- windowManager.addView(chipView, windowLayoutParams)
- chipDisplaying = true
+ // Text
+ currentChipView.requireViewById<TextView>(R.id.text).apply {
+ text = context.getString(chipType.chipText, otherDeviceName)
+ }
+
+ // Loading
+ val showLoading = chipType == ChipType.TRANSFER_INITIATED
+ currentChipView.requireViewById<View>(R.id.loading).visibility =
+ if (showLoading) { View.VISIBLE } else { View.GONE }
+
+ // Undo
+ val showUndo = chipType == ChipType.TRANSFER_SUCCEEDED
+ currentChipView.requireViewById<View>(R.id.undo).visibility =
+ if (showUndo) { View.VISIBLE } else { View.GONE }
+
+ if (oldChipView == null) {
+ windowManager.addView(chipView, windowLayoutParams)
+ }
}
private fun removeChip() {
- if (!chipDisplaying) { return }
+ if (chipView == null) { return }
windowManager.removeView(chipView)
- chipDisplaying = false
+ chipView = null
+ }
+
+ @VisibleForTesting
+ enum class ChipType(
+ @StringRes internal val chipText: Int
+ ) {
+ MOVE_CLOSER_TO_TRANSFER(R.string.media_move_closer_to_transfer),
+ TRANSFER_INITIATED(R.string.media_transfer_playing),
+ TRANSFER_SUCCEEDED(R.string.media_transfer_playing),
}
inner class AddChipCommand : Command {
- override fun execute(pw: PrintWriter, args: List<String>) = addChip()
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ val chipTypeArg = args[1]
+ ChipType.values().forEach {
+ if (it.name == chipTypeArg) {
+ displayChip(it, otherDeviceName = args[0])
+ return
+ }
+ }
+
+ pw.println("Chip type must be one of " +
+ ChipType.values().map { it.name }.reduce { acc, s -> "$acc, $s" })
+ }
+
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG")
+ pw.println(
+ "Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG <deviceName> <chipType>"
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 5afefa1..ab48a28 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.navigationbar.gestural;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import android.app.ActivityManager;
@@ -544,7 +546,8 @@
layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
layoutParams.windowAnimations = 0;
layoutParams.privateFlags |=
- WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION);
layoutParams.setTitle(TAG + mContext.getDisplayId());
layoutParams.setFitInsetsTypes(0 /* types */);
layoutParams.setTrustedOverlay();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index a16b92f..097cf35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -33,6 +33,7 @@
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
@@ -45,8 +46,6 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
@@ -75,7 +74,6 @@
import androidx.core.math.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.systemui.R;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -339,8 +337,9 @@
views = new RemoteViews(mContext.getPackageName(),
R.layout.people_tile_suppressed_layout);
}
- Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon);
- Bitmap disabledBitmap = convertDrawableToDisabledBitmap(appIcon);
+ Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon).mutate();
+ appIcon.setColorFilter(getDisabledColorFilter());
+ Bitmap disabledBitmap = convertDrawableToBitmap(appIcon);
views.setImageViewBitmap(R.id.icon, disabledBitmap);
return views;
}
@@ -1262,8 +1261,9 @@
Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
Icon icon = tile.getUserIcon();
if (icon == null) {
- Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge);
- return convertDrawableToDisabledBitmap(placeholder);
+ Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
+ placeholder.setColorFilter(getDisabledColorFilter());
+ return convertDrawableToBitmap(placeholder);
}
PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
context.getPackageManager(),
@@ -1276,10 +1276,7 @@
hasNewStory);
if (isDndBlockingTileData(tile)) {
- // If DND is blocking the conversation, then display the icon in grayscale.
- ColorMatrix colorMatrix = new ColorMatrix();
- colorMatrix.setSaturation(0);
- personDrawable.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+ personDrawable.setColorFilter(getDisabledColorFilter());
}
return convertDrawableToBitmap(personDrawable);
@@ -1375,11 +1372,4 @@
mAvatarSize = avatarSize;
}
}
-
- private static Bitmap convertDrawableToDisabledBitmap(Drawable icon) {
- Bitmap appIconAsBitmap = convertDrawableToBitmap(icon);
- FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
- drawable.setIsDisabled(true);
- return convertDrawableToBitmap(drawable);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 033fe1e..26c89ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -120,10 +120,12 @@
private ImageView mSignalIcon;
private TextView mMobileTitleText;
private TextView mMobileSummaryText;
+ private TextView mAirplaneModeSummaryText;
private Switch mMobileDataToggle;
private View mMobileToggleDivider;
private Switch mWiFiToggle;
private FrameLayout mDoneLayout;
+ private FrameLayout mAirplaneModeLayout;
private Drawable mBackgroundOn;
private Drawable mBackgroundOff = null;
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -210,9 +212,11 @@
mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
mDoneLayout = mDialogView.requireViewById(R.id.done_layout);
+ mAirplaneModeLayout = mDialogView.requireViewById(R.id.apm_layout);
mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+ mAirplaneModeSummaryText = mDialogView.requireViewById(R.id.airplane_mode_summary);
mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider);
mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
@@ -230,6 +234,8 @@
setOnClickListener();
mTurnWifiOnLayout.setBackground(null);
+ mAirplaneModeLayout.setVisibility(
+ mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mWifiRecyclerView.setAdapter(mAdapter);
}
@@ -269,6 +275,7 @@
mSeeAllLayout.setOnClickListener(null);
mWiFiToggle.setOnCheckedChangeListener(null);
mDoneLayout.setOnClickListener(null);
+ mAirplaneModeLayout.setOnClickListener(null);
mInternetDialogController.onStop();
mInternetDialogFactory.destroyDialog();
}
@@ -294,13 +301,17 @@
}
if (mInternetDialogController.isAirplaneModeEnabled()) {
mInternetDialogSubTitle.setVisibility(View.GONE);
+ mAirplaneModeLayout.setVisibility(View.VISIBLE);
} else {
+ mInternetDialogTitle.setText(getDialogTitleText());
+ mInternetDialogSubTitle.setVisibility(View.VISIBLE);
mInternetDialogSubTitle.setText(getSubtitleText());
+ mAirplaneModeLayout.setVisibility(View.GONE);
}
updateEthernet();
if (shouldUpdateMobileNetwork) {
- setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
- || mInternetDialogController.isCarrierNetworkActive());
+ setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular(),
+ mInternetDialogController.isCarrierNetworkActive());
}
if (!mCanConfigWifi) {
@@ -343,6 +354,9 @@
mWifiManager.setWifiEnabled(isChecked);
});
mDoneLayout.setOnClickListener(v -> dismiss());
+ mAirplaneModeLayout.setOnClickListener(v -> {
+ mInternetDialogController.setAirplaneModeDisabled();
+ });
}
@MainThread
@@ -351,42 +365,60 @@
mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
}
- private void setMobileDataLayout(boolean isCarrierNetworkConnected) {
- if (mInternetDialogController.isAirplaneModeEnabled()
- || !mInternetDialogController.hasCarrier()) {
+ private void setMobileDataLayout(boolean activeNetworkIsCellular,
+ boolean isCarrierNetworkActive) {
+ boolean isNetworkConnected = activeNetworkIsCellular || isCarrierNetworkActive;
+ // 1. Mobile network should be gone if airplane mode ON or the list of active
+ // subscriptionId is null.
+ // 2. Carrier network should be gone if airplane mode ON and Wi-Fi is OFF.
+ if (DEBUG) {
+ Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive);
+ }
+
+ if (!mInternetDialogController.hasActiveSubId()
+ && (!mWifiManager.isWifiEnabled() || !isCarrierNetworkActive)) {
mMobileNetworkLayout.setVisibility(View.GONE);
} else {
- mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
mMobileNetworkLayout.setVisibility(View.VISIBLE);
+ mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
mMobileTitleText.setText(getMobileNetworkTitle());
- if (!TextUtils.isEmpty(getMobileNetworkSummary())) {
+ String summary = getMobileNetworkSummary();
+ if (!TextUtils.isEmpty(summary)) {
mMobileSummaryText.setText(
- Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY));
+ Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY));
mMobileSummaryText.setVisibility(View.VISIBLE);
} else {
mMobileSummaryText.setVisibility(View.GONE);
}
-
mBackgroundExecutor.execute(() -> {
Drawable drawable = getSignalStrengthDrawable();
mHandler.post(() -> {
mSignalIcon.setImageDrawable(drawable);
});
});
- mMobileTitleText.setTextAppearance(isCarrierNetworkConnected
+ mMobileTitleText.setTextAppearance(isNetworkConnected
? R.style.TextAppearance_InternetDialog_Active
: R.style.TextAppearance_InternetDialog);
- mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected
+ int secondaryRes = isNetworkConnected
? R.style.TextAppearance_InternetDialog_Secondary_Active
- : R.style.TextAppearance_InternetDialog_Secondary);
+ : R.style.TextAppearance_InternetDialog_Secondary;
+ mMobileSummaryText.setTextAppearance(secondaryRes);
+ // Set airplane mode to the summary for carrier network
+ if (mInternetDialogController.isAirplaneModeEnabled()) {
+ mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
+ mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode));
+ mAirplaneModeSummaryText.setTextAppearance(secondaryRes);
+ } else {
+ mAirplaneModeSummaryText.setVisibility(View.GONE);
+ }
mMobileNetworkLayout.setBackground(
- isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff);
+ isNetworkConnected ? mBackgroundOn : mBackgroundOff);
TypedArray array = mContext.obtainStyledAttributes(
R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
int dividerColor = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.textColorSecondary);
- mMobileToggleDivider.setBackgroundColor(isCarrierNetworkConnected
+ mMobileToggleDivider.setBackgroundColor(isNetworkConnected
? array.getColor(0, dividerColor) : dividerColor);
array.recycle();
@@ -620,10 +652,13 @@
@WorkerThread
public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
@Nullable WifiEntry connectedEntry) {
+ // Should update the carrier network layout when it is connected under airplane mode ON.
+ boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE
+ && mInternetDialogController.isAirplaneModeEnabled();
mHandler.post(() -> {
mConnectedWifiEntry = connectedEntry;
mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
- updateDialog(false /* shouldUpdateMobileNetwork */);
+ updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */);
mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
mAdapter.notifyDataSetChanged();
});
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 2a7d2c3..6f63a08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -86,6 +86,7 @@
import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -98,8 +99,10 @@
import javax.inject.Inject;
-public class InternetDialogController implements WifiEntry.DisconnectCallback,
- AccessPointController.AccessPointCallback {
+/**
+ * Controller for Internet Dialog.
+ */
+public class InternetDialogController implements AccessPointController.AccessPointCallback {
private static final String TAG = "InternetDialogController";
private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
@@ -281,6 +284,10 @@
return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
}
+ void setAirplaneModeDisabled() {
+ mConnectivityManager.setAirplaneMode(false);
+ }
+
@VisibleForTesting
protected int getDefaultDataSubscriptionId() {
return mSubscriptionManager.getDefaultDataSubscriptionId();
@@ -352,7 +359,7 @@
if (DEBUG) {
Log.d(TAG, "No Wi-Fi item.");
}
- if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) {
+ if (!hasActiveSubId() || (!isVoiceStateInService() && !isDataStateInService())) {
if (DEBUG) {
Log.d(TAG, "No carrier or service is out of service.");
}
@@ -403,15 +410,16 @@
return drawable;
}
- if (isDataStateInService() || isVoiceStateInService()) {
+ boolean isCarrierNetworkActive = isCarrierNetworkActive();
+ if (isDataStateInService() || isVoiceStateInService() || isCarrierNetworkActive) {
AtomicReference<Drawable> shared = new AtomicReference<>();
- shared.set(getSignalStrengthDrawableWithLevel());
+ shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive));
drawable = shared.get();
}
int tintColor = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.textColorTertiary);
- if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ if (activeNetworkIsCellular() || isCarrierNetworkActive) {
tintColor = mContext.getColor(R.color.connected_network_primary_color);
}
drawable.setTint(tintColor);
@@ -426,12 +434,15 @@
*
* @return The Drawable which is a signal bar icon with level.
*/
- Drawable getSignalStrengthDrawableWithLevel() {
+ Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive) {
final SignalStrength strength = mTelephonyManager.getSignalStrength();
int level = (strength == null) ? 0 : strength.getLevel();
int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
- if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
- level += 1;
+ if ((mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId))
+ || isCarrierNetworkActive) {
+ level = isCarrierNetworkActive
+ ? SignalStrength.NUM_SIGNAL_STRENGTH_BINS
+ : (level + 1);
numLevels += 1;
}
return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
@@ -586,15 +597,18 @@
if (!isMobileDataEnabled()) {
return context.getString(R.string.mobile_data_off_summary);
}
- if (!isDataStateInService()) {
- return context.getString(R.string.mobile_data_no_connection);
- }
+
String summary = networkTypeDescription;
+ // Set network description for the carrier network when connecting to the carrier network
+ // under the airplane mode ON.
if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
summary = context.getString(R.string.preference_summary_default_combination,
context.getString(R.string.mobile_data_connection_active),
networkTypeDescription);
+ } else if (!isDataStateInService()) {
+ summary = context.getString(R.string.mobile_data_no_connection);
}
+
return summary;
}
@@ -678,7 +692,7 @@
/**
* @return whether there is the carrier item in the slice.
*/
- boolean hasCarrier() {
+ boolean hasActiveSubId() {
if (mSubscriptionManager == null) {
if (DEBUG) {
Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
@@ -870,36 +884,29 @@
return;
}
- boolean hasConnectedWifi = false;
- final int accessPointSize = accessPoints.size();
- for (int i = 0; i < accessPointSize; i++) {
- WifiEntry wifiEntry = accessPoints.get(i);
- if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
- mConnectedEntry = wifiEntry;
- hasConnectedWifi = true;
- break;
- }
- }
- if (!hasConnectedWifi) {
- mConnectedEntry = null;
- }
-
int count = MAX_WIFI_ENTRY_COUNT;
if (mHasEthernet) {
count -= 1;
}
- if (hasCarrier()) {
+ if (hasActiveSubId()) {
count -= 1;
}
- if (hasConnectedWifi) {
- count -= 1;
+ if (count > accessPoints.size()) {
+ count = accessPoints.size();
}
- final List<WifiEntry> wifiEntries = accessPoints.stream()
- .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
- || !wifiEntry.hasInternetAccess()))
- .limit(count)
- .collect(Collectors.toList());
- mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+ WifiEntry connectedEntry = null;
+ final List<WifiEntry> wifiEntries = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ WifiEntry entry = accessPoints.get(i);
+ if (connectedEntry == null && entry.isDefaultNetwork() && entry.hasInternetAccess()) {
+ connectedEntry = entry;
+ } else {
+ wifiEntries.add(entry);
+ }
+ }
+ mConnectedEntry = connectedEntry;
+ mWifiEntriesCount = wifiEntries.size();
if (mCallback != null) {
mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
@@ -910,10 +917,6 @@
public void onSettingsActivityTriggered(Intent settingsIntent) {
}
- @Override
- public void onDisconnectResult(int status) {
- }
-
private class InternetTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.DataConnectionStateListener,
TelephonyCallback.DisplayInfoListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
new file mode 100644
index 0000000..2e1762a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -0,0 +1,48 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+/**
+ * Calculates and moves the QS frame vertically.
+ */
+public abstract class QsFrameTranslateController {
+
+ protected StatusBar mStatusBar;
+
+ public QsFrameTranslateController(StatusBar statusBar) {
+ mStatusBar = statusBar;
+ }
+
+ /**
+ * Calculate and translate the QS Frame on the Y-axis.
+ */
+ public abstract void translateQsFrame(View qsFrame, QS qs, float overExpansion,
+ float qsTranslationForFullShadeTransition);
+
+ /**
+ * Calculate the top padding for notifications panel. This could be the supplied
+ * @param expansionHeight or recalculate it for a different value.
+ */
+ public abstract float getNotificationsTopPadding(float expansionHeight,
+ NotificationStackScrollLayoutController controller);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
new file mode 100644
index 0000000..c156797
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of QS Translation. This by default does not do much.
+ */
+@SysUISingleton
+public class QsFrameTranslateImpl extends QsFrameTranslateController {
+
+ @Inject
+ public QsFrameTranslateImpl(StatusBar statusBar) {
+ super(statusBar);
+ }
+
+ @Override
+ public void translateQsFrame(View qsFrame, QS qs, float overExpansion,
+ float qsTranslationForFullShadeTransition) {
+ }
+
+ @Override
+ public float getNotificationsTopPadding(float expansionHeight,
+ NotificationStackScrollLayoutController controller) {
+
+ return expansionHeight;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateModule.java
new file mode 100644
index 0000000..075adfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.statusbar;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface QsFrameTranslateModule {
+
+ @Binds
+ @SysUISingleton
+ QsFrameTranslateController bindQsFrameTranslateController(QsFrameTranslateImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 8b0252b..9e5dab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -31,11 +31,6 @@
var parent: GroupEntry?,
/**
- * Identifies the notification order in the entire notification list
- */
- var stableIndex: Int = -1,
-
- /**
* The section that this ListEntry was sorted into. If the child of the group, this will be the
* parent's section. Null if not attached to the list.
*/
@@ -53,6 +48,11 @@
var promoter: NotifPromoter?,
/**
+ * If an entry's group was pruned from the list by NotifPipeline logic, the reason is here.
+ */
+ var groupPruneReason: String?,
+
+ /**
* If the [VisualStabilityManager] is suppressing group or section changes for this entry,
* suppressedChanges will contain the new parent or section that we would have assigned to
* the entry had it not been suppressed by the VisualStabilityManager.
@@ -60,14 +60,23 @@
var suppressedChanges: SuppressedAttachState
) {
+ /**
+ * Identifies the notification order in the entire notification list.
+ * NOTE: this property is intentionally excluded from equals calculation (by not making it a
+ * constructor arg) because its value changes based on the presence of other members in the
+ * list, rather than anything having to do with this entry's attachment.
+ */
+ var stableIndex: Int = -1
+
/** Copies the state of another instance. */
fun clone(other: ListAttachState) {
parent = other.parent
- stableIndex = other.stableIndex
section = other.section
excludingFilter = other.excludingFilter
promoter = other.promoter
+ groupPruneReason = other.groupPruneReason
suppressedChanges.clone(other.suppressedChanges)
+ stableIndex = other.stableIndex
}
/** Resets back to a "clean" state (the same as created by the factory method) */
@@ -76,20 +85,22 @@
section = null
excludingFilter = null
promoter = null
- stableIndex = -1
+ groupPruneReason = null
suppressedChanges.reset()
+ stableIndex = -1
}
companion object {
@JvmStatic
fun create(): ListAttachState {
return ListAttachState(
- null,
- -1,
- null,
- null,
- null,
- SuppressedAttachState.create())
+ null,
+ null,
+ null,
+ null,
+ null,
+ SuppressedAttachState.create()
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 37eacad..915057f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -31,8 +31,6 @@
private final String mKey;
private final long mCreationTime;
- int mFirstAddedIteration = -1;
-
private final ListAttachState mPreviousAttachState = ListAttachState.create();
private final ListAttachState mAttachState = ListAttachState.create();
@@ -95,14 +93,6 @@
}
/**
- * True if this entry has been attached to the shade at least once in its lifetime (it may not
- * currently be attached).
- */
- public boolean hasBeenAttachedBefore() {
- return mFirstAddedIteration != -1;
- }
-
- /**
* Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
* fresh attach state (all entries will be null/default-initialized).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 748c69d..120c722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection;
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkState;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZE_FILTERING;
@@ -34,6 +36,7 @@
import android.annotation.Nullable;
import android.os.Trace;
import android.util.ArrayMap;
+import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -50,6 +53,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -72,6 +76,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import javax.inject.Inject;
@@ -103,7 +108,7 @@
private final List<NotifFilter> mNotifFinalizeFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
private final List<NotifSection> mNotifSections = new ArrayList<>();
- @Nullable private NotifStabilityManager mNotifStabilityManager;
+ private NotifStabilityManager mNotifStabilityManager;
private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
new ArrayList<>();
@@ -228,7 +233,7 @@
mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size()));
}
- void setNotifStabilityManager(NotifStabilityManager notifStabilityManager) {
+ void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -244,6 +249,14 @@
mNotifStabilityManager.setInvalidationListener(this::onReorderingAllowedInvalidated);
}
+ @NonNull
+ private NotifStabilityManager getStabilityManager() {
+ if (mNotifStabilityManager == null) {
+ return DefaultNotifStabilityManager.INSTANCE;
+ }
+ return mNotifStabilityManager;
+ }
+
void setComparators(List<NotifComparator> comparators) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -460,10 +473,6 @@
for (NotificationEntry entry : mAllEntries) {
entry.beginNewAttachState();
-
- if (entry.mFirstAddedIteration == -1) {
- entry.mFirstAddedIteration = mIterationCount;
- }
}
mNotifList.clear();
@@ -519,7 +528,6 @@
GroupEntry group = mGroups.get(topLevelKey);
if (group == null) {
group = new GroupEntry(topLevelKey, mSystemClock.uptimeMillis());
- group.mFirstAddedIteration = mIterationCount;
mGroups.put(topLevelKey, group);
}
if (group.getParent() == null) {
@@ -569,7 +577,7 @@
}
private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) {
- if (mNotifStabilityManager == null) {
+ if (getStabilityManager().isEveryChangeAllowed()) {
return;
}
Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs");
@@ -612,7 +620,7 @@
final GroupEntry prevParent = entry.getPreviousAttachState().getParent();
final GroupEntry assignedParent = entry.getParent();
if (prevParent != assignedParent
- && !mNotifStabilityManager.isGroupChangeAllowed(entry.getRepresentativeEntry())) {
+ && !getStabilityManager().isGroupChangeAllowed(entry.getRepresentativeEntry())) {
entry.getAttachState().getSuppressedChanges().setParent(assignedParent);
entry.setParent(prevParent);
if (prevParent == ROOT_ENTRY) {
@@ -655,56 +663,60 @@
private void pruneIncompleteGroups(List<ListEntry> shadeList) {
Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups");
+ // Any group which lost a child on this run to stability is exempt from being pruned or
+ // having its summary promoted, regardless of how many children it has
+ Set<String> groupsWithChildrenLostToStability =
+ getGroupsWithChildrenLostToStability(shadeList);
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
if (tle instanceof GroupEntry) {
final GroupEntry group = (GroupEntry) tle;
final List<NotificationEntry> children = group.getRawChildren();
+ final boolean hasSummary = group.getSummary() != null;
- if (group.getSummary() != null && children.size() == 0) {
- NotificationEntry summary = group.getSummary();
- summary.setParent(ROOT_ENTRY);
- // The list may be sorted; replace the group with the summary, in its place
- shadeList.set(i, summary);
-
- group.setSummary(null);
- annulAddition(group, shadeList);
+ if (hasSummary && children.size() == 0) {
+ if (groupsWithChildrenLostToStability.contains(group.getKey())) {
+ // This group lost a child on this run to stability, so it is exempt from
+ // having its summary promoted to the top level, so prune it.
+ // It has no children, so it will just vanish.
+ pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
+ } else {
+ // For any other summary with no children, promote the summary.
+ pruneGroupAtIndexAndPromoteSummary(shadeList, group, i);
+ }
i--; // The node we visited is gone, so be sure to visit this index again.
- } else if (group.getSummary() == null
- || children.size() < MIN_CHILDREN_FOR_GROUP) {
+ } else if (!hasSummary) {
+ // If the group doesn't provide a summary, ignore it and add
+ // any children it may have directly to top-level.
+ pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
- if (group.getSummary() != null
- && group.wasAttachedInPreviousPass()
- && mNotifStabilityManager != null
- && !mNotifStabilityManager.isGroupChangeAllowed(group.getSummary())) {
- // if this group was previously attached and group changes aren't
- // allowed, keep it around until group changes are allowed again
+ i--; // The node we visited is gone, so be sure to visit this index again.
+ } else if (children.size() < MIN_CHILDREN_FOR_GROUP) {
+ // This group has a summary and insufficient, but nonzero children.
+ checkState(hasSummary, "group must have summary at this point");
+ checkState(!children.isEmpty(), "empty group should have been promoted");
+
+ if (groupsWithChildrenLostToStability.contains(group.getKey())) {
+ // This group lost a child on this run to stability, so it is exempt from
+ // the "min children" requirement; keep it around in case more children are
+ // added before changes are allowed again.
+ group.getAttachState().getSuppressedChanges().setWasPruneSuppressed(true);
+ continue;
+ }
+ if (group.wasAttachedInPreviousPass()
+ && !getStabilityManager().isGroupChangeAllowed(group.getSummary())) {
+ checkState(!children.isEmpty(), "empty group should have been pruned");
+ // This group was previously attached and group changes aren't
+ // allowed; keep it around until group changes are allowed again.
group.getAttachState().getSuppressedChanges().setWasPruneSuppressed(true);
continue;
}
- // If the group doesn't provide a summary or is too small, ignore it and add
+ // The group is too small, ignore it and add
// its children (if any) directly to top-level.
-
- shadeList.remove(i);
-
- if (group.getSummary() != null) {
- final NotificationEntry summary = group.getSummary();
- group.setSummary(null);
- annulAddition(summary, shadeList);
- }
-
- for (int j = 0; j < children.size(); j++) {
- final NotificationEntry child = children.get(j);
- child.setParent(ROOT_ENTRY);
- // The list may be sorted, so add the children in order where the group was.
- shadeList.add(i + j, child);
- }
- children.clear();
-
- annulAddition(group, shadeList);
+ pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
i--; // The node we visited is gone, so be sure to visit this index again.
}
@@ -713,6 +725,99 @@
Trace.endSection();
}
+ private void pruneGroupAtIndexAndPromoteSummary(List<ListEntry> shadeList,
+ GroupEntry group, int index) {
+ // Validate that the group has no children
+ checkArgument(group.getChildren().isEmpty(), "group should have no children");
+
+ NotificationEntry summary = group.getSummary();
+ summary.setParent(ROOT_ENTRY);
+ // The list may be sorted; replace the group with the summary, in its place
+ ListEntry oldEntry = shadeList.set(index, summary);
+
+ // Validate that the replaced entry was the group entry
+ checkState(oldEntry == group);
+
+ group.setSummary(null);
+ annulAddition(group, shadeList);
+ summary.getAttachState().setGroupPruneReason(
+ "SUMMARY with no children @ " + mPipelineState.getStateName());
+ }
+
+ private void pruneGroupAtIndexAndPromoteAnyChildren(List<ListEntry> shadeList,
+ GroupEntry group, int index) {
+ // REMOVE the GroupEntry at this index
+ ListEntry oldEntry = shadeList.remove(index);
+
+ // Validate that the replaced entry was the group entry
+ checkState(oldEntry == group);
+
+ List<NotificationEntry> children = group.getRawChildren();
+ boolean hasSummary = group.getSummary() != null;
+
+ // Remove the group summary, if present, and leave detached.
+ if (hasSummary) {
+ final NotificationEntry summary = group.getSummary();
+ group.setSummary(null);
+ annulAddition(summary, shadeList);
+ summary.getAttachState().setGroupPruneReason(
+ "SUMMARY with too few children @ " + mPipelineState.getStateName());
+ }
+
+ // Promote any children
+ if (!children.isEmpty()) {
+ // create the reason we will report on the child for why its group was pruned.
+ String childReason = hasSummary
+ ? ("CHILD with " + (children.size() - 1) + " siblings @ "
+ + mPipelineState.getStateName())
+ : ("CHILD with no summary @ " + mPipelineState.getStateName());
+
+ // Remove children from the group and add them to the shadeList.
+ for (int j = 0; j < children.size(); j++) {
+ final NotificationEntry child = children.get(j);
+ child.setParent(ROOT_ENTRY);
+ child.getAttachState().setGroupPruneReason(requireNonNull(childReason));
+ }
+ // The list may be sorted, so add the children in order where the group was.
+ shadeList.addAll(index, children);
+ children.clear();
+ }
+
+ annulAddition(group, shadeList);
+ }
+
+ /**
+ * Collect the keys of any groups which have already lost a child to stability this run.
+ *
+ * If stability is being enforced, then {@link #stabilizeGroupingNotifs(List)} might have
+ * detached some children from their groups and left them at the top level because the child was
+ * previously attached at the top level. Doing so would set the
+ * {@link SuppressedAttachState#getParent() suppressed parent} for the current attach state.
+ *
+ * If we've already removed a child from this group, we don't want to remove any more children
+ * from the group (even if that would leave only a single notification in the group) because
+ * that could cascade over multiple runs and allow a large group of notifications all show up as
+ * top level (ungrouped) notifications.
+ */
+ @NonNull
+ private Set<String> getGroupsWithChildrenLostToStability(List<ListEntry> shadeList) {
+ if (getStabilityManager().isEveryChangeAllowed()) {
+ return Collections.emptySet();
+ }
+ ArraySet<String> groupsWithChildrenLostToStability = new ArraySet<>();
+ for (int i = 0; i < shadeList.size(); i++) {
+ final ListEntry tle = shadeList.get(i);
+ final GroupEntry suppressedParent =
+ tle.getAttachState().getSuppressedChanges().getParent();
+ if (suppressedParent != null) {
+ // This top-level-entry was supposed to be attached to this group,
+ // so mark the group as having lost a child to stability.
+ groupsWithChildrenLostToStability.add(suppressedParent.getKey());
+ }
+ }
+ return groupsWithChildrenLostToStability;
+ }
+
/**
* If a ListEntry was added to the shade list and then later removed (e.g. because it was a
* group that was broken up), this method will erase any bookkeeping traces of that addition
@@ -727,10 +832,9 @@
// lot of them), it will put the system into an inconsistent state. So we check all of them
// here.
- if (entry.getParent() == null || entry.mFirstAddedIteration == -1) {
+ if (entry.getParent() == null) {
throw new IllegalStateException(
- "Cannot nullify addition of " + entry.getKey() + ": no such addition. ("
- + entry.getParent() + " " + entry.mFirstAddedIteration + ")");
+ "Cannot nullify addition of " + entry.getKey() + ": no parent.");
}
if (entry.getParent() == ROOT_ENTRY) {
@@ -771,9 +875,6 @@
entry.setParent(null);
entry.getAttachState().setSection(null);
entry.getAttachState().setPromoter(null);
- if (entry.mFirstAddedIteration == mIterationCount) {
- entry.mFirstAddedIteration = -1;
- }
}
private void assignSections() {
@@ -804,12 +905,12 @@
assignIndexes(mNotifList);
// Check for suppressed order changes
- if (!mNotifStabilityManager.isEveryChangeAllowed()) {
+ if (!getStabilityManager().isEveryChangeAllowed()) {
mForceReorderable = true;
boolean isSorted = isSorted(mNotifList, mTopLevelComparator);
mForceReorderable = false;
if (!isSorted) {
- mNotifStabilityManager.onEntryReorderSuppressed();
+ getStabilityManager().onEntryReorderSuppressed();
}
}
Trace.endSection();
@@ -897,12 +998,26 @@
curr.getParent());
}
+ if (curr.getSuppressedChanges().getSection() != null) {
+ mLogger.logSectionChangeSuppressed(
+ mIterationCount,
+ curr.getSuppressedChanges().getSection(),
+ curr.getSection());
+ }
+
if (curr.getSuppressedChanges().getWasPruneSuppressed()) {
mLogger.logGroupPruningSuppressed(
mIterationCount,
curr.getParent());
}
+ if (!Objects.equals(curr.getGroupPruneReason(), prev.getGroupPruneReason())) {
+ mLogger.logPrunedReasonChanged(
+ mIterationCount,
+ prev.getGroupPruneReason(),
+ curr.getGroupPruneReason());
+ }
+
if (curr.getExcludingFilter() != prev.getExcludingFilter()) {
mLogger.logFilterChanged(
mIterationCount,
@@ -927,20 +1042,11 @@
prev.getSection(),
curr.getSection());
}
-
- if (curr.getSuppressedChanges().getSection() != null) {
- mLogger.logSectionChangeSuppressed(
- mIterationCount,
- curr.getSuppressedChanges().getSection(),
- curr.getSection());
- }
}
}
private void onBeginRun() {
- if (mNotifStabilityManager != null) {
- mNotifStabilityManager.onBeginRun();
- }
+ getStabilityManager().onBeginRun();
}
private void cleanupPluggables() {
@@ -953,9 +1059,7 @@
mNotifSections.get(i).getSectioner().onCleanup();
}
- if (mNotifStabilityManager != null) {
- callOnCleanup(List.of(mNotifStabilityManager));
- }
+ callOnCleanup(List.of(getStabilityManager()));
}
private void callOnCleanup(List<? extends Pluggable<?>> pluggables) {
@@ -1015,7 +1119,7 @@
private boolean mForceReorderable = false;
private boolean canReorder(ListEntry entry) {
- return mForceReorderable || mNotifStabilityManager.isEntryReorderingAllowed(entry);
+ return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
}
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
@@ -1064,12 +1168,10 @@
NotifSection finalSection = newSection;
// have we seen this entry before and are we changing its section?
- if (mNotifStabilityManager != null
- && entry.wasAttachedInPreviousPass()
- && newSection != prevAttachState.getSection()) {
+ if (entry.wasAttachedInPreviousPass() && newSection != prevAttachState.getSection()) {
// are section changes allowed?
- if (!mNotifStabilityManager.isSectionChangeAllowed(entry.getRepresentativeEntry())) {
+ if (!getStabilityManager().isSectionChangeAllowed(entry.getRepresentativeEntry())) {
// record the section that we wanted to change to
entry.getAttachState().getSuppressedChanges().setSection(newSection);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index bbb97d1..ec4e039 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -385,7 +385,7 @@
}
private boolean shouldWaitForGroupToInflate(GroupEntry group, long now) {
- if (group == GroupEntry.ROOT_ENTRY || group.hasBeenAttachedBefore()) {
+ if (group == GroupEntry.ROOT_ENTRY || group.wasAttachedInPreviousPass()) {
return false;
}
if (isBeyondGroupInitializationWindow(group, now)) {
@@ -397,11 +397,12 @@
return true;
}
for (NotificationEntry child : group.getChildren()) {
- if (mInflatingNotifs.contains(child) && !child.hasBeenAttachedBefore()) {
+ if (mInflatingNotifs.contains(child) && !child.wasAttachedInPreviousPass()) {
mLogger.logDelayingGroupRelease(group.getKey(), child.getKey());
return true;
}
}
+ mLogger.logDoneWaitingForGroupInflation(group.getKey());
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index dd4794f..f835250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -41,11 +41,19 @@
})
}
+ fun logDoneWaitingForGroupInflation(groupKey: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = groupKey
+ }, {
+ "Finished inflating all members of group $str1, releasing group"
+ })
+ }
+
fun logGroupInflationTookTooLong(groupKey: String) {
buffer.log(TAG, LogLevel.WARNING, {
str1 = groupKey
}, {
- "Group inflation took too long far $str1, releasing children early"
+ "Group inflation took too long for $str1, releasing children early"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 75489b1..327876c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -118,7 +118,7 @@
}
@Override
- public boolean isGroupChangeAllowed(NotificationEntry entry) {
+ public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
@@ -126,7 +126,7 @@
}
@Override
- public boolean isSectionChangeAllowed(NotificationEntry entry) {
+ public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isSectionChangeAllowedForEntry =
mReorderingAllowed
|| mHeadsUpManager.isAlerting(entry.getKey())
@@ -138,7 +138,7 @@
}
@Override
- public boolean isEntryReorderingAllowed(ListEntry section) {
+ public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
return mReorderingAllowed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index 798bfe7..feb48da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -18,6 +18,8 @@
import android.annotation.IntDef;
+import androidx.annotation.NonNull;
+
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import java.lang.annotation.Retention;
@@ -35,6 +37,7 @@
return state == mState;
}
+ /** Get the current state's integer representation */
public @StateName int getState() {
return mState;
}
@@ -75,6 +78,41 @@
}
}
+ /** Get the current state's string representation */
+ @NonNull
+ public String getStateName() {
+ return getStateName(mState);
+ }
+
+ /** Get the given state's string representation */
+ @NonNull
+ public static String getStateName(@StateName int state) {
+ switch (state) {
+ case STATE_IDLE:
+ return "STATE_IDLE";
+ case STATE_BUILD_STARTED:
+ return "STATE_BUILD_STARTED";
+ case STATE_RESETTING:
+ return "STATE_RESETTING";
+ case STATE_PRE_GROUP_FILTERING:
+ return "STATE_PRE_GROUP_FILTERING";
+ case STATE_GROUPING:
+ return "STATE_GROUPING";
+ case STATE_TRANSFORMING:
+ return "STATE_TRANSFORMING";
+ case STATE_GROUP_STABILIZING:
+ return "STATE_GROUP_STABILIZING";
+ case STATE_SORTING:
+ return "STATE_SORTING";
+ case STATE_FINALIZE_FILTERING:
+ return "STATE_FINALIZE_FILTERING";
+ case STATE_FINALIZING:
+ return "STATE_FINALIZING";
+ default:
+ return "STATE:" + state;
+ }
+ }
+
public static final int STATE_IDLE = 0;
public static final int STATE_BUILD_STARTED = 1;
public static final int STATE_RESETTING = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8fff905..ba3e855 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -37,9 +37,9 @@
})
}
- fun logEndBuildList(iterationCount: Int, topLevelEntries: Int, numChildren: Int) {
+ fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) {
buffer.log(TAG, INFO, {
- long1 = iterationCount.toLong()
+ long1 = buildId.toLong()
int1 = topLevelEntries
int2 = numChildren
}, {
@@ -112,21 +112,21 @@
fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) {
buffer.log(TAG, WARNING, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = groupKey
str2 = existingKey
str3 = newKey
}, {
- """(Build $int1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
+ """(Build $long1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
})
}
fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
buffer.log(TAG, WARNING, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = topLevelKey
}, {
- "(Build $int1) Duplicate top-level key: $str1"
+ "(Build $long1) Duplicate top-level key: $str1"
})
}
@@ -137,7 +137,7 @@
newParent: GroupEntry?
) {
buffer.log(TAG, INFO, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = key
str2 = prevParent?.key
str3 = newParent?.key
@@ -153,22 +153,22 @@
"MODIFIED (ATTACHED)"
}
- "(Build $int1) $action {$str1}"
+ "(Build $long1) $action {$str1}"
})
}
fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
buffer.log(TAG, INFO, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = prevParent?.key
str2 = newParent?.key
}, {
if (str1 == null && str2 != null) {
- "(Build $int1) Parent is {$str2}"
+ "(Build $long1) Parent is {$str2}"
} else if (str1 != null && str2 == null) {
- "(Build $int1) Parent was {$str1}"
+ "(Build $long1) Parent was {$str1}"
} else {
- "(Build $int1) Reparent: {$str1} -> {$str2}"
+ "(Build $long1) Reparent: {$str1} -> {$str2}"
}
})
}
@@ -179,7 +179,7 @@
keepingParent: GroupEntry?
) {
buffer.log(TAG, INFO, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = suppressedParent?.key
str2 = keepingParent?.key
}, {
@@ -192,24 +192,38 @@
keepingParent: GroupEntry?
) {
buffer.log(TAG, INFO, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = keepingParent?.key
}, {
"(Build $long1) Group pruning suppressed; keeping parent '$str1'"
})
}
+ fun logPrunedReasonChanged(
+ buildId: Int,
+ prevReason: String?,
+ newReason: String?
+ ) {
+ buffer.log(TAG, INFO, {
+ long1 = buildId.toLong()
+ str1 = prevReason
+ str2 = newReason
+ }, {
+ "(Build $long1) Pruned reason changed: $str1 -> $str2"
+ })
+ }
+
fun logFilterChanged(
buildId: Int,
prevFilter: NotifFilter?,
newFilter: NotifFilter?
) {
buffer.log(TAG, INFO, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = prevFilter?.name
str2 = newFilter?.name
}, {
- "(Build $int1) Filter changed: $str1 -> $str2"
+ "(Build $long1) Filter changed: $str1 -> $str2"
})
}
@@ -219,11 +233,11 @@
newPromoter: NotifPromoter?
) {
buffer.log(TAG, INFO, {
- int1 = buildId
+ long1 = buildId.toLong()
str1 = prevPromoter?.name
str2 = newPromoter?.name
}, {
- "(Build $int1) Promoter changed: $str1 -> $str2"
+ "(Build $long1) Promoter changed: $str1 -> $str2"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
deleted file mode 100644
index cb2d3cb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
+++ /dev/null
@@ -1,78 +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.systemui.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * Pluggable for participating in notif stabilization. In particular, suppressing group and
- * section changes.
- *
- * The stability manager should be invalidated when previously suppressed a group or
- * section change is now allowed.
- */
-public abstract class NotifStabilityManager extends Pluggable<NotifStabilityManager> {
-
- protected NotifStabilityManager(String name) {
- super(name);
- }
-
- /**
- * Called at the beginning of every pipeline run to perform any necessary cleanup from the
- * previous run.
- */
- public abstract void onBeginRun();
-
- /**
- * Returns whether this notification can currently change groups/parents.
- * Per iteration of the notification pipeline, locally stores this information until the next
- * run of the pipeline. When this method returns true, it's expected that a group change for
- * this entry is being suppressed.
- */
- public abstract boolean isGroupChangeAllowed(NotificationEntry entry);
-
- /**
- * Returns whether this notification entry can currently change sections.
- * Per iteration of the notification pipeline, locally stores this information until the next
- * run of the pipeline. When this method returns true, it's expected that a section change is
- * being suppressed.
- */
- public abstract boolean isSectionChangeAllowed(NotificationEntry entry);
-
- /**
- * Is a notification entry is allowed be reordered
- * @param entry
- * @return if can re-order
- */
- public abstract boolean isEntryReorderingAllowed(ListEntry entry);
-
- /**
- * Called by the pipeline to determine if every call to the other stability methods would
- * return true, regardless of parameters. This allows the pipeline to skip any pieces of
- * work related to stability.
- *
- * @return true if all other methods will return true for any parameters.
- */
- public abstract boolean isEveryChangeAllowed();
-
- /**
- * Called by the pipeline to inform the stability manager that an entry reordering was indeed
- * suppressed as the result of a previous call to {@link #isEntryReorderingAllowed(ListEntry)}.
- */
- public abstract void onEntryReorderSuppressed();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
new file mode 100644
index 0000000..60f557c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.systemui.statusbar.notification.collection.listbuilder.pluggable
+
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Pluggable for participating in notif stabilization. In particular, suppressing group and
+ * section changes.
+ *
+ * The stability manager should be invalidated when previously suppressed a group or
+ * section change is now allowed.
+ */
+abstract class NotifStabilityManager protected constructor(name: String) :
+ Pluggable<NotifStabilityManager>(name) {
+ /**
+ * Called at the beginning of every pipeline run to perform any necessary cleanup from the
+ * previous run.
+ */
+ abstract fun onBeginRun()
+
+ /**
+ * Returns whether this notification can currently change groups/parents.
+ * Per iteration of the notification pipeline, locally stores this information until the next
+ * run of the pipeline. When this method returns false, it's expected that a group change for
+ * this entry is being suppressed.
+ */
+ abstract fun isGroupChangeAllowed(entry: NotificationEntry): Boolean
+
+ /**
+ * Returns whether this notification entry can currently change sections.
+ * Per iteration of the notification pipeline, locally stores this information until the next
+ * run of the pipeline. When this method returns false, it's expected that a section change is
+ * being suppressed.
+ */
+ abstract fun isSectionChangeAllowed(entry: NotificationEntry): Boolean
+
+ /**
+ * Returns whether this list entry is allowed to be reordered within its section.
+ * Unlike [isGroupChangeAllowed] or [isSectionChangeAllowed], this method is called on every
+ * entry, so an implementation may not assume that returning false means an order change is
+ * being suppressed. However, if an order change is suppressed, that will be reported to ths
+ * implementation by calling [onEntryReorderSuppressed] after ordering is complete.
+ */
+ abstract fun isEntryReorderingAllowed(entry: ListEntry): Boolean
+
+ /**
+ * Called by the pipeline to determine if every call to the other stability methods would
+ * return true, regardless of parameters. This allows the pipeline to skip any pieces of
+ * work related to stability.
+ *
+ * @return true if all other methods will return true for any parameters.
+ */
+ abstract fun isEveryChangeAllowed(): Boolean
+
+ /**
+ * Called by the pipeline to inform the stability manager that an entry reordering was indeed
+ * suppressed as the result of a previous call to [.isEntryReorderingAllowed].
+ */
+ abstract fun onEntryReorderSuppressed()
+}
+
+/** The default, no-op instance of the stability manager which always allows all changes */
+object DefaultNotifStabilityManager : NotifStabilityManager("DefaultNotifStabilityManager") {
+ override fun onBeginRun() {}
+ override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true
+ override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true
+ override fun isEntryReorderingAllowed(entry: ListEntry): Boolean = true
+ override fun isEveryChangeAllowed(): Boolean = true
+ override fun onEntryReorderSuppressed() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index db68ecf1..275074d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -157,6 +157,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -339,6 +340,7 @@
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
@VisibleForTesting QS mQs;
private FrameLayout mQsFrame;
+ private QsFrameTranslateController mQsFrameTranslateController;
@Nullable
private CommunalHostViewController mCommunalViewController;
private KeyguardStatusViewController mKeyguardStatusViewController;
@@ -775,7 +777,8 @@
NotificationRemoteInputManager remoteInputManager,
Optional<SysUIUnfoldComponent> unfoldComponent,
ControlsComponent controlsComponent,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ QsFrameTranslateController qsFrameTranslateController) {
super(view,
falsingManager,
dozeLog,
@@ -900,7 +903,6 @@
mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
-
mCommunalSourceCallback = () -> {
mUiExecutor.execute(() -> setCommunalSource(null /*source*/));
};
@@ -908,7 +910,7 @@
mCommunalSourceMonitorCallback = (source) -> {
mUiExecutor.execute(() -> setCommunalSource(source));
};
-
+ mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
onFinishInflate();
}
@@ -2667,7 +2669,8 @@
(float) (mQsMaxExpansionHeight),
computeQsExpansionFraction());
} else {
- return mQsExpansionHeight;
+ return mQsFrameTranslateController.getNotificationsTopPadding(mQsExpansionHeight,
+ mNotificationStackScrollLayoutController);
}
}
@@ -3251,8 +3254,8 @@
}
private void updateQsFrameTranslation() {
- float translation = mOverExpansion / 2.0f + mQsTranslationForFullShadeTransition;
- mQsFrame.setTranslationY(translation);
+ mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, mOverExpansion,
+ mQsTranslationForFullShadeTransition);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 256b069..ec7e93b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -43,7 +43,6 @@
val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
- val viewCenterProvider = StatusBarViewsCenterProvider()
val viewsToAnimate = arrayOf(
statusBarLeftSide,
systemIconArea
@@ -52,7 +51,7 @@
mView.viewTreeObserver.addOnPreDrawListener(object :
ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
- animationController.onViewsReady(viewsToAnimate, viewCenterProvider)
+ animationController.onViewsReady(viewsToAnimate)
mView.viewTreeObserver.removeOnPreDrawListener(this)
return true
}
@@ -82,7 +81,7 @@
mView.importantForAccessibility = mode
}
- private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
+ class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
override fun getViewCenter(view: View, outPoint: Point) =
when (view.id) {
R.id.status_bar_left_side -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 805ddf5..6d033477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -18,7 +18,7 @@
import android.view.View
import android.view.WindowManager
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -27,20 +27,18 @@
@SysUIUnfoldScope
class StatusBarMoveFromCenterAnimationController @Inject constructor(
private val progressProvider: ScopedUnfoldTransitionProgressProvider,
- private val windowManager: WindowManager
+ windowManager: WindowManager
) {
private val transitionListener = TransitionListener()
- private var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator? = null
+ private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
+ viewCenterProvider = StatusBarViewsCenterProvider())
- fun onViewsReady(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
- moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
- viewCenterProvider = viewCenterProvider)
-
- moveFromCenterAnimator?.updateDisplayProperties()
+ fun onViewsReady(viewsToAnimate: Array<View>) {
+ moveFromCenterAnimator.updateDisplayProperties()
viewsToAnimate.forEach {
- moveFromCenterAnimator?.registerViewForAnimation(it)
+ moveFromCenterAnimator.registerViewForAnimation(it)
}
progressProvider.addCallback(transitionListener)
@@ -48,24 +46,23 @@
fun onViewDetached() {
progressProvider.removeCallback(transitionListener)
- moveFromCenterAnimator?.clearRegisteredViews()
- moveFromCenterAnimator = null
+ moveFromCenterAnimator.clearRegisteredViews()
}
fun onStatusBarWidthChanged() {
- moveFromCenterAnimator?.updateDisplayProperties()
- moveFromCenterAnimator?.updateViewPositions()
+ moveFromCenterAnimator.updateDisplayProperties()
+ moveFromCenterAnimator.updateViewPositions()
}
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- moveFromCenterAnimator?.onTransitionProgress(progress)
+ moveFromCenterAnimator.onTransitionProgress(progress)
}
override fun onTransitionFinished() {
// Reset translations when transition is stopped/cancelled
// (e.g. the transition could be cancelled mid-way when rotating the screen)
- moveFromCenterAnimator?.onTransitionProgress(1f)
+ moveFromCenterAnimator.onTransitionProgress(1f)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
deleted file mode 100644
index ac8b47d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.app.StatusBarManager;
-import android.content.Context;
-import android.content.res.Configuration;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import javax.inject.Inject;
-
-/**
- * Let {@link RemoteInputView} to control the visibility of QuickSetting.
- */
-@SysUISingleton
-public class RemoteInputQuickSettingsDisabler
- implements ConfigurationController.ConfigurationListener {
-
- private Context mContext;
- @VisibleForTesting boolean mRemoteInputActive;
- @VisibleForTesting boolean misLandscape;
- private int mLastOrientation;
- private final CommandQueue mCommandQueue;
-
- @Inject
- public RemoteInputQuickSettingsDisabler(Context context,
- ConfigurationController configController, CommandQueue commandQueue) {
- mContext = context;
- mCommandQueue = commandQueue;
- mLastOrientation = mContext.getResources().getConfiguration().orientation;
- configController.addCallback(this);
- }
-
- public int adjustDisableFlags(int state) {
- if (mRemoteInputActive && misLandscape) {
- state |= StatusBarManager.DISABLE2_QUICK_SETTINGS;
- }
-
- return state;
- }
-
- public void setRemoteInputActive(boolean active){
- if(mRemoteInputActive != active){
- mRemoteInputActive = active;
- recomputeDisableFlags();
- }
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- if (newConfig.orientation != mLastOrientation) {
- misLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
- mLastOrientation = newConfig.orientation;
- recomputeDisableFlags();
- }
- }
-
- /**
- * Reapplies the disable flags. Then the method adjustDisableFlags in this class will be invoked
- * in {@link QSFragment#disable(int, int, boolean)} and
- * {@link StatusBar#disable(int, int, boolean)}
- * to modify the disable flags according to the status of mRemoteInputActive and misLandscape.
- */
- private void recomputeDisableFlags() {
- mCommandQueue.recomputeDisableFlags(mContext.getDisplayId(), true);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
new file mode 100644
index 0000000..31ef2f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/**
+ * Controls whether the disable flag [StatusBarManager.DISABLE2_QUICK_SETTINGS] should be set.
+ * This would happen when a [RemoteInputView] is active, the device is in landscape and not using
+ * split shade.
+ */
+@SysUISingleton
+class RemoteInputQuickSettingsDisabler @Inject constructor(
+ private val context: Context,
+ private val commandQueue: CommandQueue,
+ configController: ConfigurationController
+) : ConfigurationController.ConfigurationListener {
+
+ private var remoteInputActive = false
+ private var isLandscape: Boolean
+ private var shouldUseSplitNotificationShade: Boolean
+
+ init {
+ isLandscape =
+ context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ shouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(context.resources)
+ configController.addCallback(this)
+ }
+
+ fun adjustDisableFlags(state: Int): Int {
+ var mutableState = state
+ if (remoteInputActive &&
+ isLandscape &&
+ !shouldUseSplitNotificationShade
+ ) {
+ mutableState = state or StatusBarManager.DISABLE2_QUICK_SETTINGS
+ }
+ return mutableState
+ }
+
+ fun setRemoteInputActive(active: Boolean) {
+ if (remoteInputActive != active) {
+ remoteInputActive = active
+ recomputeDisableFlags()
+ }
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ var needToRecompute = false
+
+ val newIsLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+ if (newIsLandscape != isLandscape) {
+ isLandscape = newIsLandscape
+ needToRecompute = true
+ }
+
+ val newSplitShadeFlag = Utils.shouldUseSplitNotificationShade(context.resources)
+ if (newSplitShadeFlag != shouldUseSplitNotificationShade) {
+ shouldUseSplitNotificationShade = newSplitShadeFlag
+ needToRecompute = true
+ }
+ if (needToRecompute) {
+ recomputeDisableFlags()
+ }
+ }
+
+ /**
+ * Called in order to trigger a refresh of the disable flags after a relevant configuration
+ * change or when a [RemoteInputView] has changed its active state. The method
+ * [adjustDisableFlags] will be invoked to modify the disable flags according to
+ * [remoteInputActive], [isLandscape] and [shouldUseSplitNotificationShade].
+ */
+ private fun recomputeDisableFlags() {
+ commandQueue.recomputeDisableFlags(context.displayId, true)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 8b394bf..97fce51 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -30,8 +30,6 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
@@ -42,6 +40,7 @@
import android.widget.TextView;
import com.android.internal.R;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
import com.android.launcher3.icons.IconFactory;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -280,9 +279,14 @@
final ApplicationInfo appInfo = appEntry.info;
UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
IconFactory iconFactory = IconFactory.obtain(context);
- Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
- appInfo.loadUnbadgedIcon(packageManager), user, true).icon;
- return new BitmapDrawable(context.getResources(), iconBmp);
+ try {
+ return iconFactory.createBadgedIconBitmap(
+ appInfo.loadUnbadgedIcon(packageManager),
+ new IconOptions().setUser(user))
+ .newIcon(context);
+ } finally {
+ iconFactory.recycle();
+ }
}
private static boolean showApplicationIcon(ApplicationInfo appInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 51de1321..e6fc49f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.unfold
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
@@ -111,7 +112,7 @@
Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
try {
// Add the view only if we are unfolding and this is the first screen on
- if (!isFolded && !isUnfoldHandled) {
+ if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
addView(onOverlayReady)
isUnfoldHandled = true
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
index fb8efa9..abc2099 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
@@ -16,12 +16,14 @@
package com.android.systemui.communal;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.communal.CommunalManager;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
@@ -36,7 +38,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class CommunalManagerUpdaterTest extends SysuiTestCase {
private CommunalSourceMonitor mMonitor;
@Mock
@@ -49,9 +50,16 @@
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(CommunalManager.class, mCommunalManager);
+ doAnswer(invocation -> {
+ final CommunalConditionsMonitor.Callback callback = invocation.getArgument(0);
+ callback.onConditionsChanged(true);
+ return null;
+ }).when(mCommunalConditionsMonitor).addCallback(any());
+
mMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor);
updater.start();
+ clearInvocations(mCommunalManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
index 475dde2..dea42f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
@@ -18,15 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
-import android.content.Context;
+import android.content.res.Resources;
import androidx.test.filters.SmallTest;
@@ -51,13 +50,14 @@
public class FeatureFlagsReleaseTest extends SysuiTestCase {
FeatureFlagsRelease mFeatureFlagsRelease;
+ @Mock private Resources mResources;
@Mock private DumpManager mDumpManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlagsRelease = new FeatureFlagsRelease(mDumpManager);
+ mFeatureFlagsRelease = new FeatureFlagsRelease(mResources, mDumpManager);
}
@After
@@ -68,15 +68,34 @@
}
@Test
+ public void testBooleanResourceFlag() {
+ int flagId = 213;
+ int flagResourceId = 3;
+ ResourceBooleanFlag flag = new ResourceBooleanFlag(flagId, flagResourceId);
+ when(mResources.getBoolean(flagResourceId)).thenReturn(true);
+
+ assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue();
+ }
+
+ @Test
public void testDump() {
+ int flagIdA = 213;
+ int flagIdB = 18;
+ int flagResourceId = 3;
+ BooleanFlag flagA = new BooleanFlag(flagIdA, true);
+ ResourceBooleanFlag flagB = new ResourceBooleanFlag(flagIdB, flagResourceId);
+ when(mResources.getBoolean(flagResourceId)).thenReturn(true);
+
// WHEN the flags have been accessed
- assertFalse(mFeatureFlagsRelease.isEnabled(1, false));
- assertTrue(mFeatureFlagsRelease.isEnabled(2, true));
+ assertThat(mFeatureFlagsRelease.isEnabled(1, false)).isFalse();
+ assertThat(mFeatureFlagsRelease.isEnabled(flagA)).isTrue();
+ assertThat(mFeatureFlagsRelease.isEnabled(flagB)).isTrue();
// THEN the dump contains the flags and the default values
String dump = dumpToString();
assertThat(dump).contains(" sysui_flag_1: false\n");
- assertThat(dump).contains(" sysui_flag_2: true\n");
+ assertThat(dump).contains(" sysui_flag_" + flagIdA + ": true\n");
+ assertThat(dump).contains(" sysui_flag_" + flagIdB + ": true\n");
}
private String dumpToString() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 41ce941..7cc0172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -19,6 +19,7 @@
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
import android.graphics.drawable.RippleDrawable
import android.media.MediaMetadata
import android.media.session.MediaSession
@@ -97,6 +98,7 @@
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var mediaFlags: MediaFlags
private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -123,6 +125,7 @@
private lateinit var session: MediaSession
private val device = MediaDeviceData(true, null, DEVICE_NAME)
private val disabledDevice = MediaDeviceData(false, null, "Disabled Device")
+ private lateinit var mediaData: MediaData
private val clock = FakeSystemClock()
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -135,7 +138,7 @@
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
- mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
+ mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Mock out a view holder for the player to attach to.
@@ -165,14 +168,19 @@
whenever(holder.totalTimeView).thenReturn(totalTimeView)
action0 = ImageButton(context)
whenever(holder.action0).thenReturn(action0)
+ whenever(holder.getAction(R.id.action0)).thenReturn(action0)
action1 = ImageButton(context)
whenever(holder.action1).thenReturn(action1)
+ whenever(holder.getAction(R.id.action1)).thenReturn(action1)
action2 = ImageButton(context)
whenever(holder.action2).thenReturn(action2)
+ whenever(holder.getAction(R.id.action2)).thenReturn(action2)
action3 = ImageButton(context)
whenever(holder.action3).thenReturn(action3)
+ whenever(holder.getAction(R.id.action3)).thenReturn(action3)
action4 = ImageButton(context)
whenever(holder.action4).thenReturn(action4)
+ whenever(holder.getAction(R.id.action4)).thenReturn(action4)
whenever(holder.longPressText).thenReturn(longPressText)
whenever(longPressText.handler).thenReturn(handler)
settings = View(context)
@@ -200,6 +208,26 @@
setPlaybackState(playbackBuilder.build())
}
session.setActive(true)
+
+ mediaData = MediaData(
+ userId = USER_ID,
+ initialized = true,
+ backgroundColor = BG_COLOR,
+ app = APP,
+ appIcon = null,
+ artist = ARTIST,
+ song = TITLE,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = PACKAGE,
+ token = session.sessionToken,
+ clickIntent = null,
+ device = device,
+ active = true,
+ resumeAction = null)
+
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
}
@After
@@ -210,18 +238,50 @@
@Test
fun bindWhenUnattached() {
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, null, null, device, true, null)
+ val state = mediaData.copy(token = null)
player.bindPlayer(state, PACKAGE)
assertThat(player.isPlaying()).isFalse()
}
@Test
+ fun bindSemanticActions() {
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+ val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+ val semanticActions = MediaButton(
+ playOrPause = MediaAction(icon, Runnable {}, "play"),
+ nextOrCustom = MediaAction(icon, Runnable {}, "next"),
+ startCustom = MediaAction(icon, null, "custom 1"),
+ endCustom = MediaAction(icon, null, "custom 2")
+ )
+ val state = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(holder)
+ player.bindPlayer(state, PACKAGE)
+
+ verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
+ assertThat(action0.contentDescription).isEqualTo("custom 1")
+ assertThat(action0.isEnabled()).isFalse()
+
+ verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
+ assertThat(action1.isEnabled()).isFalse()
+
+ verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
+ assertThat(action2.isEnabled()).isTrue()
+ assertThat(action2.contentDescription).isEqualTo("play")
+
+ verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
+ assertThat(action3.isEnabled()).isTrue()
+ assertThat(action3.contentDescription).isEqualTo("next")
+
+ verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
+ assertThat(action4.contentDescription).isEqualTo("custom 2")
+ assertThat(action4.isEnabled()).isFalse()
+ }
+
+ @Test
fun bindText() {
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
- player.bindPlayer(state, PACKAGE)
+ player.bindPlayer(mediaData, PACKAGE)
assertThat(titleText.getText()).isEqualTo(TITLE)
assertThat(artistText.getText()).isEqualTo(ARTIST)
}
@@ -229,9 +289,7 @@
@Test
fun bindDevice() {
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
- player.bindPlayer(state, PACKAGE)
+ player.bindPlayer(mediaData, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
assertThat(seamless.isEnabled()).isTrue()
@@ -242,8 +300,7 @@
seamless.id = 1
val fallbackString = context.getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
+ val state = mediaData.copy(device = disabledDevice)
player.bindPlayer(state, PACKAGE)
assertThat(seamless.isEnabled()).isFalse()
assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -254,8 +311,7 @@
fun bindNullDevice() {
val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
+ val state = mediaData.copy(device = null)
player.bindPlayer(state, PACKAGE)
assertThat(seamless.isEnabled()).isTrue()
assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -265,9 +321,7 @@
@Test
fun bindDeviceResumptionPlayer() {
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
- resumption = true)
+ val state = mediaData.copy(resumption = true)
player.bindPlayer(state, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
assertThat(seamless.isEnabled()).isFalse()
@@ -323,9 +377,7 @@
fun dismissButtonClick() {
val mediaKey = "key for dismissal"
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
- notificationKey = KEY)
+ val state = mediaData.copy(notificationKey = KEY)
player.bindPlayer(state, mediaKey)
assertThat(dismiss.isEnabled).isEqualTo(true)
@@ -337,9 +389,7 @@
fun dismissButtonDisabled() {
val mediaKey = "key for dismissal"
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
- isClearable = false, notificationKey = KEY)
+ val state = mediaData.copy(isClearable = false, notificationKey = KEY)
player.bindPlayer(state, mediaKey)
assertThat(dismiss.isEnabled).isEqualTo(false)
@@ -351,9 +401,7 @@
whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
player.attachPlayer(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
- notificationKey = KEY)
+ val state = mediaData.copy(notificationKey = KEY)
player.bindPlayer(state, mediaKey)
assertThat(dismiss.isEnabled).isEqualTo(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 09c83e5..7a487b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -74,8 +74,9 @@
mManager = new MediaDataCombineLatest();
mManager.addListener(mListener);
- mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
- new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null,
+ mMediaData = new MediaData(
+ USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 5a3c43c..6b203bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -96,11 +96,24 @@
setUser(USER_MAIN)
// Set up test media data
- dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, null, null, device, true, null)
-
- dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
- emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+ dataMain = MediaData(
+ userId = USER_MAIN,
+ initialized = true,
+ backgroundColor = BG_COLOR,
+ app = APP,
+ appIcon = null,
+ artist = ARTIST,
+ song = TITLE,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = PACKAGE,
+ token = null,
+ clickIntent = null,
+ device = device,
+ active = true,
+ resumeAction = null)
+ dataGuest = dataMain.copy(userId = USER_GUEST)
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
`when`(smartspaceData.isActive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index e2019e0..d0b957c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -11,12 +11,15 @@
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
+import android.media.session.PlaybackState
import android.os.Bundle
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
@@ -67,6 +70,7 @@
@JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock lateinit var mediaControllerFactory: MediaControllerFactory
@Mock lateinit var controller: MediaController
+ @Mock lateinit var transportControls: MediaController.TransportControls
@Mock lateinit var playbackInfo: MediaController.PlaybackInfo
lateinit var session: MediaSession
lateinit var metadataBuilder: MediaMetadata.Builder
@@ -87,6 +91,7 @@
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
+ @Mock private lateinit var mediaFlags: MediaFlags
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -122,7 +127,8 @@
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- tunerService = tunerService
+ tunerService = tunerService,
+ mediaFlags = mediaFlags
)
verify(tunerService).addTunable(capture(tunableCaptor),
eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -140,6 +146,7 @@
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+ whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(playbackInfo.playbackType).thenReturn(
MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
@@ -161,6 +168,7 @@
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
}
@After
@@ -583,4 +591,157 @@
assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
MediaDataManager.MAX_COMPACT_ACTIONS)
}
+
+ @Test
+ fun testPlaybackActions_noState_usesNotification() {
+ val desc = "Notification Action"
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+ whenever(controller.playbackState).thenReturn(null)
+
+ val notifWithAction = SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.addAction(android.R.drawable.ic_media_play, desc, null)
+ }
+ build()
+ }
+ mediaDataManager.onNotificationAdded(KEY, notifWithAction)
+
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0))
+
+ assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
+ assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
+ assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
+ }
+
+ @Test
+ fun testPlaybackActions_hasPrevNext() {
+ val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+ val stateActions = PlaybackState.ACTION_PLAY or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder = PlaybackState.Builder()
+ .setActions(stateActions)
+ customDesc.forEach {
+ stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+ }
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0))
+
+ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+ val actions = mediaDataCaptor.value!!.semanticActions!!
+
+ assertThat(actions.playOrPause).isNotNull()
+ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_play))
+ actions.playOrPause!!.action!!.run()
+ verify(transportControls).play()
+
+ assertThat(actions.prevOrCustom).isNotNull()
+ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_prev))
+ actions.prevOrCustom!!.action!!.run()
+ verify(transportControls).skipToPrevious()
+
+ assertThat(actions.nextOrCustom).isNotNull()
+ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_next))
+ actions.nextOrCustom!!.action!!.run()
+ verify(transportControls).skipToNext()
+
+ assertThat(actions.startCustom).isNotNull()
+ assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+ assertThat(actions.endCustom).isNotNull()
+ assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+ }
+
+ @Test
+ fun testPlaybackActions_noPrevNext_usesCustom() {
+ val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+ val stateActions = PlaybackState.ACTION_PLAY
+ val stateBuilder = PlaybackState.Builder()
+ .setActions(stateActions)
+ customDesc.forEach {
+ stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+ }
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0))
+
+ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+ val actions = mediaDataCaptor.value!!.semanticActions!!
+
+ assertThat(actions.playOrPause).isNotNull()
+ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_play))
+
+ assertThat(actions.prevOrCustom).isNotNull()
+ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+ assertThat(actions.nextOrCustom).isNotNull()
+ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
+
+ assertThat(actions.startCustom).isNotNull()
+ assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2])
+
+ assertThat(actions.endCustom).isNotNull()
+ assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3])
+ }
+
+ @Test
+ fun testPlaybackActions_reservedSpace() {
+ val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+ val stateActions = PlaybackState.ACTION_PLAY
+ val stateBuilder = PlaybackState.Builder()
+ .setActions(stateActions)
+ customDesc.forEach {
+ stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+ }
+ val extras = Bundle().apply {
+ putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
+ putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
+ }
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ whenever(controller.extras).thenReturn(extras)
+
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0))
+
+ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+ val actions = mediaDataCaptor.value!!.semanticActions!!
+
+ assertThat(actions.playOrPause).isNotNull()
+ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_play))
+
+ assertThat(actions.prevOrCustom).isNull()
+ assertThat(actions.nextOrCustom).isNull()
+
+ assertThat(actions.startCustom).isNotNull()
+ assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+ assertThat(actions.endCustom).isNotNull()
+ assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7dadbad..3d59497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -102,9 +102,24 @@
// Create a media sesssion and notification for testing.
session = MediaSession(context, SESSION_KEY)
- mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
- emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
- device = null, active = true, resumeAction = null)
+ mediaData = MediaData(
+ userId = USER_ID,
+ initialized = true,
+ backgroundColor = 0,
+ app = PACKAGE,
+ appIcon = null,
+ artist = null,
+ song = SESSION_TITLE,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = PACKAGE,
+ token = session.sessionToken,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null)
+
whenever(controllerFactory.create(session.sessionToken))
.thenReturn(controller)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index 421f9be..ceeb0db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -163,8 +163,27 @@
isPlaying: Boolean?,
location: Int,
resumption: Boolean
- ) =
- MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(),
- "package:" + app, null, null, null, true, null, location, resumption, "key:" + app,
- false, isPlaying)
+ ) = MediaData(
+ userId = 0,
+ initialized = false,
+ backgroundColor = 0,
+ app = app,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "package: $app",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null,
+ playbackLocation = location,
+ resumption = resumption,
+ notificationKey = "key: $app",
+ hasCheckedForResume = false,
+ isPlaying = isPlaying
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index de2235d..8c2fed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -95,13 +95,26 @@
setPlaybackState(playbackBuilder.build())
}
session.setActive(true)
- mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
- emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
- device = null, active = true, resumeAction = null)
- resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
- emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
- device = null, active = false, resumeAction = null, resumption = true)
+ mediaData = MediaData(
+ userId = USER_ID,
+ initialized = true,
+ backgroundColor = 0,
+ app = PACKAGE,
+ appIcon = null,
+ artist = null,
+ song = SESSION_TITLE,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = PACKAGE,
+ token = session.sessionToken,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null)
+
+ resumeData = mediaData.copy(token = null, active = false, resumption = true)
}
@Test
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 efb4931..923f018 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
@@ -16,14 +16,20 @@
package com.android.systemui.media.taptotransfer
+import android.view.View
import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -48,7 +54,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- mediaTttChipController = MediaTttChipController(context, commandRegistry, windowManager)
+ mediaTttChipController = MediaTttChipController(commandRegistry, context, windowManager)
}
@Test(expected = IllegalStateException::class)
@@ -71,24 +77,24 @@
@Test
fun addChipCommand_chipAdded() {
- commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
verify(windowManager).addView(any(), any())
}
@Test
fun addChipCommand_twice_chipNotAddedTwice() {
- commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
reset(windowManager)
- commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
verify(windowManager, never()).addView(any(), any())
}
@Test
fun removeChipCommand_chipRemoved() {
// First, add the chip
- commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
// Then, remove it
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
@@ -103,6 +109,104 @@
verify(windowManager, never()).removeView(any())
}
+ @Test
+ fun moveCloserToTransfer_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+
+ val chipView = getChipView()
+ assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferInitiated_chipTextContainsDeviceName_loadingIcon_noUndo() {
+ commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+
+ val chipView = getChipView()
+ assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferSucceeded_chipTextContainsDeviceName_noLoadingIcon_undo() {
+ commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+ val chipView = getChipView()
+ assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+ assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+ commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+
+ assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
+ commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+ commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+ assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
+ commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+ commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+ assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
+ commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+ commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+
+ assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.GONE)
+ }
+
+ private fun getMoveCloserToTransferCommand(): Array<String> =
+ arrayOf(
+ MediaTttChipController.ADD_CHIP_COMMAND_TAG,
+ DEVICE_NAME,
+ MediaTttChipController.ChipType.MOVE_CLOSER_TO_TRANSFER.name
+ )
+
+ private fun getTransferInitiatedCommand(): Array<String> =
+ arrayOf(
+ MediaTttChipController.ADD_CHIP_COMMAND_TAG,
+ DEVICE_NAME,
+ MediaTttChipController.ChipType.TRANSFER_INITIATED.name
+ )
+
+ private fun getTransferSucceededCommand(): Array<String> =
+ arrayOf(
+ MediaTttChipController.ADD_CHIP_COMMAND_TAG,
+ DEVICE_NAME,
+ MediaTttChipController.ChipType.TRANSFER_SUCCEEDED.name
+ )
+
+ private fun LinearLayout.getChipText(): String =
+ (this.requireViewById<TextView>(R.id.text)).text as String
+
+ 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 getChipView(): LinearLayout {
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ return viewCaptor.value as LinearLayout
+ }
+
class EmptyCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
}
@@ -111,3 +215,5 @@
}
}
}
+
+private const val DEVICE_NAME = "My Tablet"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index c1562c1..6f4e619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -185,8 +185,8 @@
protected Fragment instantiate(Context context, String className, Bundle arguments) {
CommandQueue commandQueue = new CommandQueue(context);
return new QSFragment(
- new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
- commandQueue),
+ new RemoteInputQuickSettingsDisabler(context, commandQueue,
+ mock(ConfigurationController.class)),
mock(QSTileHost.class),
mock(StatusBarStateController.class),
commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index ca8903b..95e7a98c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1,5 +1,7 @@
package com.android.systemui.qs.tiles.dialog;
+import static android.provider.Settings.Global.AIRPLANE_MODE_ON;
+
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
@@ -19,7 +21,6 @@
import static org.mockito.Mockito.when;
import android.animation.Animator;
-import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
@@ -37,7 +38,6 @@
import android.view.View;
import android.view.WindowManager;
-import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -47,7 +47,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -70,7 +69,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -78,8 +76,6 @@
public class InternetDialogControllerTest extends SysuiTestCase {
private static final int SUB_ID = 1;
- private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
- private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
//SystemUIToast
private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
@@ -142,7 +138,7 @@
private DialogLaunchAnimator mDialogLaunchAnimator;
private TestableResources mTestableResources;
- private MockInternetDialogController mInternetDialogController;
+ private InternetDialogController mInternetDialogController;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private List<WifiEntry> mAccessPoints = new ArrayList<>();
private List<WifiEntry> mWifiEntries = new ArrayList<>();
@@ -170,7 +166,7 @@
when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
- mInternetDialogController = new MockInternetDialogController(mContext,
+ mInternetDialogController = new InternetDialogController(mContext,
mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
mSubscriptionManager, mTelephonyManager, mWifiManager,
mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
@@ -225,7 +221,7 @@
@Test
public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
getResourcesString("airplane_mode")));
@@ -233,7 +229,7 @@
@Test
public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
getResourcesString("quick_settings_internet_label")));
@@ -241,14 +237,14 @@
@Test
public void getSubtitleText_withAirplaneModeOn_returnNull() {
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
}
@Test
public void getSubtitleText_withWifiOff_returnWifiIsOff() {
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(false);
assertThat(mInternetDialogController.getSubtitleText(false))
@@ -263,7 +259,7 @@
@Test
public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
@@ -280,7 +276,7 @@
@Test
public void getSubtitleText_withWifiEntry_returnTapToConnect() {
// The preconditions WiFi Entries is already in setUp()
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
assertThat(mInternetDialogController.getSubtitleText(false))
@@ -295,7 +291,7 @@
@Test
public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
when(mKeyguardStateController.isUnlocked()).thenReturn(false);
@@ -305,7 +301,7 @@
@Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
@@ -319,7 +315,7 @@
@Test
public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
@@ -420,7 +416,7 @@
@Test
public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mConnectedEntry);
@@ -433,7 +429,7 @@
@Test
public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mWifiEntry1);
@@ -448,7 +444,7 @@
@Test
public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mConnectedEntry);
mAccessPoints.add(mWifiEntry1);
@@ -463,7 +459,7 @@
@Test
public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mConnectedEntry);
mAccessPoints.add(mWifiEntry1);
@@ -480,7 +476,7 @@
@Test
public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mConnectedEntry);
mAccessPoints.add(mWifiEntry1);
@@ -497,7 +493,7 @@
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
@@ -508,7 +504,7 @@
@Test
public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mConnectedEntry);
mAccessPoints.add(mWifiEntry1);
@@ -526,7 +522,7 @@
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
@@ -537,7 +533,7 @@
@Test
public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(true);
+ fakeAirplaneModeEnabled(true);
mAccessPoints.clear();
mAccessPoints.add(mWifiEntry1);
mAccessPoints.add(mWifiEntry2);
@@ -566,7 +562,7 @@
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
- mInternetDialogController.setAirplaneModeEnabled(false);
+ fakeAirplaneModeEnabled(false);
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
@@ -576,6 +572,23 @@
}
@Test
+ public void onAccessPointsChanged_wifiIsDefaultButNoInternetAccess_putIntoWifiEntries() {
+ reset(mInternetDialogCallback);
+ mAccessPoints.clear();
+ when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+ when(mWifiEntry1.isDefaultNetwork()).thenReturn(true);
+ when(mWifiEntry1.hasInternetAccess()).thenReturn(false);
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
.thenReturn(true);
@@ -641,38 +654,7 @@
mContext.getPackageName());
}
- private class MockInternetDialogController extends InternetDialogController {
-
- private GlobalSettings mGlobalSettings;
- private boolean mIsAirplaneModeOn;
-
- MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
- ActivityStarter starter, AccessPointController accessPointController,
- SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
- @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
- @Main Handler handler, @Main Executor mainExecutor,
- BroadcastDispatcher broadcastDispatcher,
- KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
- KeyguardStateController keyguardStateController, WindowManager windowManager,
- ToastFactory toastFactory, Handler workerHandler,
- CarrierConfigTracker carrierConfigTracker,
- LocationController locationController,
- DialogLaunchAnimator dialogLaunchAnimator) {
- super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
- telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
- broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
- keyguardStateController, windowManager, toastFactory, workerHandler,
- carrierConfigTracker, locationController, dialogLaunchAnimator);
- mGlobalSettings = globalSettings;
- }
-
- @Override
- boolean isAirplaneModeEnabled() {
- return mIsAirplaneModeOn;
- }
-
- public void setAirplaneModeEnabled(boolean enabled) {
- mIsAirplaneModeOn = enabled;
- }
+ private void fakeAirplaneModeEnabled(boolean enabled) {
+ when(mGlobalSettings.getInt(eq(AIRPLANE_MODE_ON), anyInt())).thenReturn(enabled ? 1 : 0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 339d5bb..0cf063f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -80,6 +80,7 @@
private RecyclerView mWifiList;
private LinearLayout mSeeAll;
private LinearLayout mWifiScanNotify;
+ private TextView mAirplaneModeSummaryText;
@Before
public void setUp() {
@@ -114,6 +115,7 @@
mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
+ mAirplaneModeSummaryText = mDialogView.requireViewById(R.id.airplane_mode_summary);
}
@After
@@ -191,7 +193,41 @@
}
@Test
- public void updateDialog_withApmOn_mobileDataLayoutGone() {
+ public void updateDialog_apmOffAndNotCarrierNetwork_mobileDataLayoutGone() {
+ // Mobile network should be gone if the list of active subscriptionId is null.
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasActiveSubId()).thenReturn(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayout() {
+ // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+
+ // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndNoCarrierNetwork_mobileDataLayoutGone() {
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
mInternetDialog.updateDialog(true);
@@ -200,6 +236,51 @@
}
@Test
+ public void updateDialog_apmOnAndWifiOnHasCarrierNetwork_showAirplaneSummary() {
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ mInternetDialog.mConnectedWifiEntry = null;
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndWifiOnHasCarrierNetwork_notShowApmSummary() {
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ mInternetDialog.mConnectedWifiEntry = null;
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndHasCarrierNetwork_notShowApmSummary() {
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndNoCarrierNetwork_notShowApmSummary() {
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
// The preconditions WiFi ON and Internet WiFi are already in setUp()
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 82cd9fa..b832577 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -114,6 +114,7 @@
private List<NotificationEntry> mEntrySet = new ArrayList<>();
private List<ListEntry> mBuiltList;
private TestableStabilityManager mStabilityManager;
+ private TestableNotifFilter mFinalizeFilter;
private Map<String, Integer> mNextIdMap = new ArrayMap<>();
private int mNextRank = 0;
@@ -136,6 +137,8 @@
mStabilityManager = spy(new TestableStabilityManager());
mListBuilder.setNotifStabilityManager(mStabilityManager);
+ mFinalizeFilter = spy(new TestableNotifFilter());
+ mListBuilder.addFinalizeFilter(mFinalizeFilter);
Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture());
mReadyForBuildListener = Objects.requireNonNull(mBuildListenerCaptor.getValue());
@@ -408,7 +411,6 @@
// THEN the summary has a null parent and an unset firstAddedIteration
assertNull(mEntrySet.get(1).getParent());
- assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
}
@Test
@@ -1037,7 +1039,7 @@
}
@Test
- public void testStabilizeGroupsDoesNotAllowGrouping() {
+ public void testStabilizeGroupsDoesNotAllowGroupingExistingNotifications() {
// GIVEN one group child without a summary yet
addGroupChild(0, PACKAGE_1, GROUP_1);
@@ -1056,7 +1058,10 @@
// because group changes aren't allowed by the stability manager
verifyBuiltList(
notif(0),
- notif(2)
+ group(
+ summary(1),
+ child(2)
+ )
);
}
@@ -1110,11 +1115,13 @@
dispatchBuild();
- // THEN all notifications are top-level and the summary doesn't show yet
- // because group changes aren't allowed by the stability manager
+ // THEN first notification stays top-level but the other notifications are grouped.
verifyBuiltList(
notif(0),
- notif(2),
+ group(
+ summary(1),
+ child(2)
+ ),
group(
summary(3),
child(4),
@@ -1209,6 +1216,140 @@
}
@Test
+ public void testStabilityIsolationAllowsGroupToHaveSingleChild() {
+ // GIVEN a group with only one child was already drawn
+ addGroupSummary(0, PACKAGE_1, GROUP_1);
+ addGroupChild(1, PACKAGE_1, GROUP_1);
+
+ dispatchBuild();
+ // NOTICE that the group is pruned and the child is moved to the top level
+ verifyBuiltList(
+ notif(1) // group with only one child is promoted
+ );
+
+ // WHEN another child is added while group changes are disabled.
+ mStabilityManager.setAllowGroupChanges(false);
+ addGroupChild(2, PACKAGE_1, GROUP_1);
+
+ dispatchBuild();
+
+ // THEN the new child should be added to the group
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(2)
+ ),
+ notif(1)
+ );
+ }
+
+ @Test
+ public void testStabilityIsolationExemptsGroupWithFinalizeFilteredChildFromShowingSummary() {
+ // GIVEN a group with only one child was already drawn
+ addGroupSummary(0, PACKAGE_1, GROUP_1);
+ addGroupChild(1, PACKAGE_1, GROUP_1);
+
+ dispatchBuild();
+ // NOTICE that the group is pruned and the child is moved to the top level
+ verifyBuiltList(
+ notif(1) // group with only one child is promoted
+ );
+
+ // WHEN another child is added but still filtered while group changes are disabled.
+ mStabilityManager.setAllowGroupChanges(false);
+ mFinalizeFilter.mIndicesToFilter.add(2);
+ addGroupChild(2, PACKAGE_1, GROUP_1);
+
+ dispatchBuild();
+
+ // THEN the new child should be shown without the summary
+ verifyBuiltList(
+ notif(1) // previously promoted child
+ );
+ }
+
+ @Test
+ public void testStabilityIsolationOfRemovedChildDoesNotExemptGroupFromPrune() {
+ // GIVEN a group with only one child was already drawn
+ addGroupSummary(0, PACKAGE_1, GROUP_1);
+ addGroupChild(1, PACKAGE_1, GROUP_1);
+
+ dispatchBuild();
+ // NOTICE that the group is pruned and the child is moved to the top level
+ verifyBuiltList(
+ notif(1) // group with only one child is promoted
+ );
+
+ // WHEN a new child is added and the old one gets filtered while group changes are disabled.
+ mStabilityManager.setAllowGroupChanges(false);
+ mFinalizeFilter.mIndicesToFilter.add(1);
+ addGroupChild(2, PACKAGE_1, GROUP_1);
+
+ dispatchBuild();
+
+ // THEN the new child should be shown without a group
+ verifyBuiltList(
+ notif(2) // previously promoted child
+ );
+ }
+
+ @Test
+ public void testFinalizeFilteredSummaryPromotesChildren() {
+ // GIVEN a group with only one child was already drawn
+ addGroupSummary(0, PACKAGE_1, GROUP_1);
+ addGroupChild(1, PACKAGE_1, GROUP_1);
+ addGroupChild(2, PACKAGE_1, GROUP_1);
+
+ // WHEN the parent is filtered out at the finalize step
+ mFinalizeFilter.mIndicesToFilter.add(0);
+
+ dispatchBuild();
+
+ // THEN the children should be promoted to the top level
+ verifyBuiltList(
+ notif(1),
+ notif(2)
+ );
+ }
+
+ @Test
+ public void testFinalizeFilteredChildrenPromotesSummary() {
+ // GIVEN a group with only one child was already drawn
+ addGroupSummary(0, PACKAGE_1, GROUP_1);
+ addGroupChild(1, PACKAGE_1, GROUP_1);
+ addGroupChild(2, PACKAGE_1, GROUP_1);
+
+ // WHEN the parent is filtered out at the finalize step
+ mFinalizeFilter.mIndicesToFilter.add(1);
+ mFinalizeFilter.mIndicesToFilter.add(2);
+
+ dispatchBuild();
+
+ // THEN the children should be promoted to the top level
+ verifyBuiltList(
+ notif(0)
+ );
+ }
+
+ @Test
+ public void testFinalizeFilteredChildPromotesSibling() {
+ // GIVEN a group with only one child was already drawn
+ addGroupSummary(0, PACKAGE_1, GROUP_1);
+ addGroupChild(1, PACKAGE_1, GROUP_1);
+ addGroupChild(2, PACKAGE_1, GROUP_1);
+
+ // WHEN the parent is filtered out at the finalize step
+ mFinalizeFilter.mIndicesToFilter.add(1);
+
+ dispatchBuild();
+
+ // THEN the children should be promoted to the top level
+ verifyBuiltList(
+ notif(2)
+ );
+ }
+
+ @Test
public void testBrokenGroupNotificationOrdering() {
// GIVEN two group children with different sections & without a summary yet
addGroupChild(0, PACKAGE_2, GROUP_1);
@@ -1228,30 +1369,6 @@
}
@Test
- public void testStabilizeGroupsHidesGroupSummary() {
- // GIVEN one group child with a summary
- addGroupChild(0, PACKAGE_1, GROUP_1);
- addGroupSummary(1, PACKAGE_1, GROUP_1);
-
- dispatchBuild(); // group summary is hidden because it needs at least 2 children to group
-
- // GIVEN visual stability manager doesn't allow any group changes
- mStabilityManager.setAllowGroupChanges(false);
-
- // WHEN we run the pipeline with the addition of a child
- addGroupChild(2, PACKAGE_1, GROUP_1);
-
- dispatchBuild();
-
- // THEN the children notifications are top-level and the summary still doesn't show yet
- // because group changes aren't allowed by the stability manager
- verifyBuiltList(
- notif(0),
- notif(2)
- );
- }
-
- @Test
public void testStabilizeGroupsDelayedSummaryRendersAllNotifsTopLevel() {
// GIVEN group children posted without a summary
addGroupChild(0, PACKAGE_1, GROUP_1);
@@ -1269,13 +1386,12 @@
dispatchBuild();
- // THEN all entries are top-level since group changes aren't allowed
+ // THEN all entries are top-level, but summary is suppressed
verifyBuiltList(
notif(0),
notif(1),
notif(2),
- notif(3),
- notif(4)
+ notif(3)
);
// WHEN visual stability manager allows group changes again
@@ -1907,6 +2023,19 @@
}
}
+ private class TestableNotifFilter extends NotifFilter {
+ ArrayList<Integer> mIndicesToFilter = new ArrayList<>();
+
+ protected TestableNotifFilter() {
+ super("TestFilter");
+ }
+
+ @Override
+ public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
+ return mIndicesToFilter.stream().anyMatch(i -> notif(i).entry == entry);
+ }
+ }
+
private static class TestableStabilityManager extends NotifStabilityManager {
boolean mAllowGroupChanges = true;
boolean mAllowSectionChanges = true;
@@ -1937,17 +2066,17 @@
}
@Override
- public boolean isGroupChangeAllowed(NotificationEntry entry) {
+ public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
return mAllowGroupChanges;
}
@Override
- public boolean isSectionChangeAllowed(NotificationEntry entry) {
+ public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
return mAllowSectionChanges;
}
@Override
- public boolean isEntryReorderingAllowed(ListEntry entry) {
+ public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
return mAllowEntryReodering;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 3ec9629..99800e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -120,6 +120,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -354,6 +355,8 @@
private InteractionJankMonitor mInteractionJankMonitor;
@Mock
private NotificationsQSContainerController mNotificationsQSContainerController;
+ @Mock
+ private QsFrameTranslateController mQsFrameTranslateController;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -530,7 +533,8 @@
mNotificationRemoteInputManager,
mSysUIUnfoldComponent,
mControlsComponent,
- mInteractionJankMonitor);
+ mInteractionJankMonitor,
+ mQsFrameTranslateController);
mNotificationPanelViewController.initDependencies(
mStatusBar,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index dc32007..7d266e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -102,7 +102,7 @@
verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
argumentCaptor.value.onPreDraw()
- verify(moveFromCenterAnimation).onViewsReady(any(), any())
+ verify(moveFromCenterAnimation).onViewsReady(any())
}
private fun createViewMock(): PhoneStatusBarView {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
new file mode 100644
index 0000000..1ce7ff4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Point
+import android.view.Display
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var windowManager: WindowManager
+
+ @Mock
+ private lateinit var display: Display
+
+ private val view: View = View(context)
+ private val progressProvider = TestUnfoldTransitionProvider()
+ private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
+
+ private lateinit var controller: StatusBarMoveFromCenterAnimationController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(windowManager.defaultDisplay).thenReturn(display)
+ `when`(display.rotation).thenReturn(Surface.ROTATION_0)
+ `when`(display.getSize(any())).thenAnswer {
+ val point = it.arguments[0] as Point
+ point.x = 100
+ point.y = 100
+ Unit
+ }
+
+ scopedProvider.setReadyToHandleTransition(true)
+
+ controller = StatusBarMoveFromCenterAnimationController(scopedProvider, windowManager)
+ }
+
+ @Test
+ fun onTransitionProgressAndFinished_resetsTranslations() {
+ controller.onViewsReady(arrayOf(view))
+
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinished()
+
+ assertThat(view.translationX).isZero()
+ }
+
+ @Test
+ fun onTransitionProgress_updatesTranslations() {
+ controller.onViewsReady(arrayOf(view))
+
+ progressProvider.onTransitionProgress(0.5f)
+
+ assertThat(view.translationX).isNonZero()
+ }
+
+ @Test
+ fun onTransitionProgress_whenDetached_doesNotUpdateTranslations() {
+ controller.onViewsReady(arrayOf(view))
+ controller.onViewDetached()
+
+ progressProvider.onTransitionProgress(0.5f)
+
+ assertThat(view.translationX).isZero()
+ }
+
+ @Test
+ fun detachedAfterProgress_resetsTranslations() {
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionProgress(0.5f)
+
+ controller.onViewDetached()
+
+ assertThat(view.translationX).isZero()
+ }
+
+ @Test
+ fun transitionFinished_viewReAttached_noChangesToTranslation() {
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinished()
+ controller.onViewDetached()
+
+ controller.onViewsReady(arrayOf(view))
+ controller.onStatusBarWidthChanged()
+
+ assertThat(view.translationX).isZero()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
deleted file mode 100644
index b359b9c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RemoteInputQuickSettingsDisablerTest extends SysuiTestCase {
-
- @Mock
- private CommandQueue mCommandQueue;
- private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mRemoteInputQuickSettingsDisabler = new RemoteInputQuickSettingsDisabler(mContext,
- mock(ConfigurationController.class), mCommandQueue);
- }
-
- @Test
- public void shouldEnableQuickSetting_afterDeactiviate() {
- mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
- mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
- assertFalse(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
- verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
- }
-
- @Test
- public void shouldDisableQuickSetting_afteActiviate() {
- mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
- mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
- assertTrue(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
- verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
- }
-
- @Test
- public void testChangeToLandscape() {
- Configuration c = new Configuration(mContext.getResources().getConfiguration());
- c.orientation = Configuration.ORIENTATION_PORTRAIT;
- mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
- c.orientation = Configuration.ORIENTATION_LANDSCAPE;
- mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
- assertTrue(mRemoteInputQuickSettingsDisabler.misLandscape);
- verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
- }
-
- @Test
- public void testChangeToPortrait() {
- Configuration c = new Configuration(mContext.getResources().getConfiguration());
- c.orientation = Configuration.ORIENTATION_LANDSCAPE;
- mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
- c.orientation = Configuration.ORIENTATION_PORTRAIT;
- mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
- assertFalse(mRemoteInputQuickSettingsDisabler.misLandscape);
- verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
new file mode 100644
index 0000000..1ab0582
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() {
+
+ @Mock lateinit var commandQueue: CommandQueue
+ private lateinit var remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler
+ private lateinit var configuration: Configuration
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ remoteInputQuickSettingsDisabler = RemoteInputQuickSettingsDisabler(
+ mContext,
+ commandQueue, Mockito.mock(ConfigurationController::class.java)
+ )
+ configuration = Configuration(mContext.resources.configuration)
+
+ // Default these conditions to what they need to be to disable QS.
+ mContext.orCreateTestableResources
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */false)
+ remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
+ configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+ }
+
+ @Test
+ fun whenRemoteInputActiveAndLandscapeAndNotSplitShade_shouldDisableQs() {
+ assertThat(
+ shouldDisableQs(
+ remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun whenRemoteInputNotActive_shouldNotDisableQs() {
+ remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+ assertThat(
+ shouldDisableQs(
+ remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun whenSplitShadeEnabled_shouldNotDisableQs() {
+ mContext.orCreateTestableResources
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */true)
+ remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+ assertThat(
+ shouldDisableQs(
+ remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun whenPortrait_shouldNotDisableQs() {
+ configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+ remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+ assertThat(
+ shouldDisableQs(
+ remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun whenRemoteInputChanges_recomputeTriggered() {
+ remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+ verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+ anyInt(), anyBoolean()
+ )
+ }
+
+ @Test
+ fun whenConfigChanges_recomputeTriggered() {
+ configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+ remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+ verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+ anyInt(), anyBoolean()
+ )
+ }
+
+ private fun shouldDisableQs(state: Int): Boolean {
+ return state and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
new file mode 100644
index 0000000..db7a8516
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.systemui.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var contentResolver: ContentResolver
+
+ @Mock
+ lateinit var sourceProvider: UnfoldTransitionProgressProvider
+
+ @Mock
+ lateinit var sinkProvider: TransitionProgressListener
+
+ lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+
+ private val sourceProviderListenerCaptor =
+ ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+
+ private val animatorDurationScaleListenerCaptor =
+ ArgumentCaptor.forClass(ContentObserver::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ progressProvider = ScaleAwareTransitionProgressProvider(
+ sourceProvider,
+ contentResolver
+ )
+
+ verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
+ verify(contentResolver).registerContentObserver(any(), any(),
+ animatorDurationScaleListenerCaptor.capture())
+
+ progressProvider.addCallback(sinkProvider)
+ }
+
+ @Test
+ fun onTransitionStarted_animationsEnabled_eventReceived() {
+ setAnimationsEnabled(true)
+
+ source.onTransitionStarted()
+
+ verify(sinkProvider).onTransitionStarted()
+ }
+
+ @Test
+ fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
+ setAnimationsEnabled(false)
+
+ source.onTransitionStarted()
+
+ verifyNoMoreInteractions(sinkProvider)
+ }
+
+ @Test
+ fun onTransitionEnd_animationsEnabled_eventReceived() {
+ setAnimationsEnabled(true)
+
+ source.onTransitionFinished()
+
+ verify(sinkProvider).onTransitionFinished()
+ }
+
+ @Test
+ fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
+ setAnimationsEnabled(false)
+
+ source.onTransitionFinished()
+
+ verifyNoMoreInteractions(sinkProvider)
+ }
+
+ @Test
+ fun onTransitionProgress_animationsEnabled_eventReceived() {
+ setAnimationsEnabled(true)
+
+ source.onTransitionProgress(42f)
+
+ verify(sinkProvider).onTransitionProgress(42f)
+ }
+
+ @Test
+ fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
+ setAnimationsEnabled(false)
+
+ source.onTransitionProgress(42f)
+
+ verifyNoMoreInteractions(sinkProvider)
+ }
+
+ private fun setAnimationsEnabled(enabled: Boolean) {
+ val durationScale = if (enabled) {
+ 1f
+ } else {
+ 0f
+ }
+ ValueAnimator.setDurationScale(durationScale)
+ animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+ }
+
+ private val source: TransitionProgressListener
+ get() = sourceProviderListenerCaptor.value
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index dda1c4f..cd338a4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -16,9 +16,9 @@
package com.android.server.accessibility.magnification;
-import static android.accessibilityservice.MagnificationConfig.DEFAULT_MODE;
-import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
-import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_DEFAULT;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
@@ -66,14 +66,14 @@
public @NonNull MagnificationConfig getMagnificationConfig(int displayId) {
final int mode = getControllingMode(displayId);
MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
final FullScreenMagnificationController fullScreenMagnificationController =
mController.getFullScreenMagnificationController();
builder.setMode(mode)
.setScale(fullScreenMagnificationController.getScale(displayId))
.setCenterX(fullScreenMagnificationController.getCenterX(displayId))
.setCenterY(fullScreenMagnificationController.getCenterY(displayId));
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
final WindowMagnificationManager windowMagnificationManager =
mController.getWindowMagnificationMgr();
builder.setMode(mode)
@@ -103,14 +103,14 @@
}
int configMode = config.getMode();
- if (configMode == DEFAULT_MODE) {
+ if (configMode == MAGNIFICATION_MODE_DEFAULT) {
configMode = getControllingMode(displayId);
}
- if (configMode == FULLSCREEN_MODE) {
+ if (configMode == MAGNIFICATION_MODE_FULLSCREEN) {
return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
config.getCenterX(), config.getCenterY(),
animate, id);
- } else if (configMode == WINDOW_MODE) {
+ } else if (configMode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
config.getScale(), config.getCenterX(), config.getCenterY());
}
@@ -141,9 +141,9 @@
*/
public float getScale(int displayId) {
int mode = getControllingMode(displayId);
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
return mController.getFullScreenMagnificationController().getScale(displayId);
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().getScale(displayId);
}
return 0;
@@ -161,7 +161,7 @@
*/
public float getCenterX(int displayId, boolean canControlMagnification) {
int mode = getControllingMode(displayId);
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
try {
@@ -171,7 +171,7 @@
unregister(displayId);
}
}
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().getCenterX(displayId);
}
return 0;
@@ -189,7 +189,7 @@
*/
public float getCenterY(int displayId, boolean canControlMagnification) {
int mode = getControllingMode(displayId);
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
try {
@@ -199,7 +199,7 @@
unregister(displayId);
}
}
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().getCenterY(displayId);
}
return 0;
@@ -221,9 +221,9 @@
public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
boolean canControlMagnification) {
int mode = getControllingMode(displayId);
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
outRegion);
}
@@ -268,9 +268,9 @@
*/
public boolean reset(int displayId, boolean animate) {
int mode = getControllingMode(displayId);
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
return mController.getFullScreenMagnificationController().reset(displayId, animate);
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().reset(displayId);
}
return false;
@@ -290,9 +290,9 @@
*/
public boolean isMagnifying(int displayId) {
int mode = getControllingMode(displayId);
- if (mode == FULLSCREEN_MODE) {
+ if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
return mController.getFullScreenMagnificationController().isMagnifying(displayId);
- } else if (mode == WINDOW_MODE) {
+ } else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
}
return false;
@@ -308,14 +308,14 @@
public int getControllingMode(int displayId) {
if (mController.isActivated(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
- return WINDOW_MODE;
+ return MAGNIFICATION_MODE_WINDOW;
} else if (mController.isActivated(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
- return FULLSCREEN_MODE;
+ return MAGNIFICATION_MODE_FULLSCREEN;
} else {
return (mController.getLastActivatedMode() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
- ? WINDOW_MODE
- : FULLSCREEN_MODE;
+ ? MAGNIFICATION_MODE_WINDOW
+ : MAGNIFICATION_MODE_FULLSCREEN;
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 049018cf..e0cd472 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -406,9 +406,8 @@
@Override
public List<AssociationInfo> getAssociations(String packageName, int userId) {
- final int callingUid = getCallingUserId();
if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
- throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
+ throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ "permissions to get associations for u" + userId + "/" + packageName);
}
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0d38fa3..45097f0 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -154,14 +154,14 @@
}
static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
- if (getCallingUserId() == SYSTEM_UID) return true;
+ if (getCallingUid() == SYSTEM_UID) return true;
return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
}
static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
@Nullable String message) {
- if (getCallingUserId() == SYSTEM_UID) return;
+ if (getCallingUid() == SYSTEM_UID) return;
context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9351415..ba6854b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -121,6 +121,7 @@
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
"java/com/android/server/policy/EventLogTags.logtags",
+ ":services.connectivity-nsd-sources",
],
libs: [
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1929df8..c7f4b4d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1598,6 +1598,8 @@
} else if (vol.type == VolumeInfo.TYPE_STUB) {
if (vol.disk.isStubVisible()) {
vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+ } else {
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_READ;
}
vol.mountUserId = mCurrentUserId;
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
@@ -3842,14 +3844,15 @@
final boolean primary = false;
final boolean removable = false;
final boolean emulated = true;
+ final boolean stub = false;
final boolean allowMassStorage = false;
final long maxFileSize = 0;
final UserHandle user = new UserHandle(userId);
final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
final String description = mContext.getString(android.R.string.unknownName);
- res.add(new StorageVolume(id, path, path, description, primary, removable,
- emulated, allowMassStorage, maxFileSize, user, null /*uuid */, id, envState));
+ res.add(new StorageVolume(id, path, path, description, primary, removable, emulated,
+ stub, allowMassStorage, maxFileSize, user, null /*uuid */, id, envState));
}
if (!foundPrimary) {
@@ -3864,6 +3867,7 @@
final boolean primary = true;
final boolean removable = primaryPhysical;
final boolean emulated = !primaryPhysical;
+ final boolean stub = false;
final boolean allowMassStorage = false;
final long maxFileSize = 0L;
final UserHandle owner = new UserHandle(userId);
@@ -3872,7 +3876,7 @@
final String state = Environment.MEDIA_REMOVED;
res.add(0, new StorageVolume(id, path, path,
- description, primary, removable, emulated,
+ description, primary, removable, emulated, stub,
allowMassStorage, maxFileSize, owner, uuid, fsUuid, state));
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 03a4d84..5fc11e8 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -130,6 +130,7 @@
private static IBatteryStats sService;
+ private final PowerProfile mPowerProfile;
final BatteryStatsImpl mStats;
private final BatteryUsageStatsStore mBatteryUsageStatsStore;
private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
@@ -343,13 +344,15 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mPowerProfile = new PowerProfile(context);
+
mStats = new BatteryStatsImpl(systemDir, handler, this,
this, mUserManagerUserInfoProvider);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
- mStats.setPowerProfileLocked(new PowerProfile(context));
+ mStats.setPowerProfileLocked(mPowerProfile);
mStats.startTrackingSystemServerCpuTime();
if (BATTERY_USAGE_STORE_ENABLED) {
@@ -2464,6 +2467,7 @@
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider);
+ checkinStats.setPowerProfileLocked(mPowerProfile);
checkinStats.readSummaryFromParcel(in);
in.recycle();
checkinStats.dumpProtoLocked(
@@ -2504,6 +2508,7 @@
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider);
+ checkinStats.setPowerProfileLocked(mPowerProfile);
checkinStats.readSummaryFromParcel(in);
in.recycle();
checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 47e24b1..5a54332 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1500,7 +1500,7 @@
int schedGroup;
int procState;
int cachedAdjSeq;
- int capability = 0;
+ int capability = cycleReEval ? app.mState.getCurCapability() : 0;
boolean foregroundActivities = false;
boolean hasVisibleActivities = false;
@@ -1891,10 +1891,6 @@
}
if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
- if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
- continue;
- }
-
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
capability |= cstate.getCurCapability();
}
@@ -1915,6 +1911,10 @@
}
}
+ if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
+ continue;
+ }
+
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
// If the other app is cached for any reason, for purposes here
// we are going to consider it empty. The specific cached state
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 6a26bea..b47ea4f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -551,9 +551,9 @@
logd("canBeSpatialized false due to usage:" + attributes.getUsage());
return false;
}
- AudioDeviceAttributes[] devices =
- // going through adapter to take advantage of routing cache
- (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+ AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+ // going through adapter to take advantage of routing cache
+ mASA.getDevicesForAttributes(attributes).toArray(devices);
final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
logd("canBeSpatialized returning " + able);
return able;
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index 90246f8..5a6c6a5 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -1,13 +1,13 @@
{
"presubmit-large": [
{
- "name": "CtsMediaTestCases",
+ "name": "CtsMediaAudioTestCases",
"options": [
{
- "include-filter": "android.media.cts.AudioManagerTest"
+ "include-filter": "android.media.audio.cts.AudioManagerTest"
},
{
- "include-filter": "android.media.cts.AudioFocusTest"
+ "include-filter": "android.media.audio.cts.AudioFocusTest"
}
]
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9499e51..3c6b096 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2750,7 +2750,7 @@
return;
}
final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
- if (targetWindow != null && mLastImeTargetWindow != targetWindow) {
+ if (targetWindow != null) {
mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
}
mLastImeTargetWindow = targetWindow;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 99fdb2d..f65deeb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5073,7 +5073,7 @@
final DumpFilter filter = DumpFilter.parseFromArguments(args);
final long token = Binder.clearCallingIdentity();
try {
- final ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions =
+ final ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions =
getAllUsersNotificationPermissions();
if (filter.stats) {
dumpJson(pw, filter, pkgPermissions);
@@ -5911,17 +5911,18 @@
// Returns a single map containing that info keyed by (uid, package name) for all users.
// Because this calls into mPermissionHelper, this method must never be called with a lock held.
@VisibleForTesting
- protected ArrayMap<Pair<Integer, String>, Boolean> getAllUsersNotificationPermissions() {
+ protected ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
+ getAllUsersNotificationPermissions() {
// don't bother if migration is not enabled
if (!mEnableAppSettingMigration) {
return null;
}
- ArrayMap<Pair<Integer, String>, Boolean> allPermissions = new ArrayMap<>();
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> allPermissions = new ArrayMap<>();
final List<UserInfo> allUsers = mUm.getUsers();
// for each of these, get the package notification permissions that are associated
// with this user and add it to the map
for (UserInfo ui : allUsers) {
- ArrayMap<Pair<Integer, String>, Boolean> userPermissions =
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> userPermissions =
mPermissionHelper.getNotificationPermissionValues(
ui.getUserHandle().getIdentifier());
for (Pair<Integer, String> pair : userPermissions.keySet()) {
@@ -5932,7 +5933,7 @@
}
private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
JSONObject dump = new JSONObject();
try {
dump.put("service", "Notification Manager");
@@ -5956,7 +5957,7 @@
}
private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mNotificationLock) {
int N = mNotificationList.size();
@@ -6047,7 +6048,7 @@
}
void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
pw.print("Current Notification Manager state");
if (filter.filtered) {
pw.print(" (filtered to "); pw.print(filter); pw.print(")");
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index f53bb75..e64ec77 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -36,10 +36,8 @@
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -139,15 +137,17 @@
return granted;
}
+ // Key: (uid, package name); Value: (granted, user set)
public @NonNull
- ArrayMap<Pair<Integer, String>, Boolean> getNotificationPermissionValues(
- int userId) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
+ getNotificationPermissionValues(int userId) {
assertFlag();
- ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId);
Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId);
for (Pair<Integer, String> pair : allRequestingUids) {
- notifPermissions.put(pair, allApprovedUids.contains(pair));
+ notifPermissions.put(pair, new Pair(allApprovedUids.contains(pair),
+ isPermissionUserSet(pair.second /* package name */, userId)));
}
return notifPermissions;
}
@@ -196,6 +196,18 @@
return false;
}
+ boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
+ assertFlag();
+ try {
+ int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
+ userId);
+ return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not reach system server", e);
+ }
+ return false;
+ }
+
private void assertFlag() {
if (!mMigrationEnabled) {
throw new IllegalStateException("Method called without checking flag value");
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4e1279c..49e223c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -557,7 +557,7 @@
out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
out.endTag(null, TAG_STATUS_ICONS);
}
- ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
if (mPermissionHelper.isMigrationEnabled() && forBackup) {
notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId);
}
@@ -573,9 +573,8 @@
out.attribute(null, ATT_NAME, r.pkg);
if (!notifPermissions.isEmpty()) {
Pair<Integer, String> app = new Pair(r.uid, r.pkg);
- out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app)
- ? IMPORTANCE_DEFAULT
- : IMPORTANCE_NONE);
+ out.attributeInt(null, ATT_IMPORTANCE,
+ notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
notifPermissions.remove(app);
} else {
if (r.importance != DEFAULT_IMPORTANCE) {
@@ -642,7 +641,7 @@
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, app.second);
out.attributeInt(null, ATT_IMPORTANCE,
- notifPermissions.get(app) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+ notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
out.endTag(null, TAG_PACKAGE);
}
}
@@ -1932,7 +1931,7 @@
public void dump(PrintWriter pw, String prefix,
@NonNull NotificationManagerService.DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
pw.print(prefix);
pw.println("per-package config version: " + XML_VERSION);
@@ -1946,7 +1945,7 @@
public void dump(ProtoOutputStream proto,
@NonNull NotificationManagerService.DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
synchronized (mPackagePreferences) {
dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter,
mPackagePreferences, pkgPermissions);
@@ -1958,7 +1957,7 @@
private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
@NonNull NotificationManagerService.DumpFilter filter,
ArrayMap<String, PackagePreferences> packagePreferences,
- ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions) {
// Used for tracking which package preferences we've seen already for notification
// permission reasons; after handling packages with local preferences, we'll want to dump
// the ones with notification permissions set but not local prefs.
@@ -1987,8 +1986,10 @@
if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
pw.print(" importance=");
pw.print(NotificationListenerService.Ranking.importanceToString(
- packagePermissions.get(key)
+ packagePermissions.get(key).first
? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+ pw.print(" userSet=");
+ pw.print(packagePermissions.get(key).second);
pkgsWithPermissionsToHandle.remove(key);
}
}
@@ -2042,7 +2043,10 @@
pw.print(')');
pw.print(" importance=");
pw.print(NotificationListenerService.Ranking.importanceToString(
- packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+ packagePermissions.get(p).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+ pw.print(" userSet=");
+ pw.print(packagePermissions.get(p).second);
pw.println();
}
}
@@ -2052,7 +2056,7 @@
private void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId,
@NonNull NotificationManagerService.DumpFilter filter,
ArrayMap<String, PackagePreferences> packagePreferences,
- ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions) {
Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
if (packagePermissions != null) {
pkgsWithPermissionsToHandle = packagePermissions.keySet();
@@ -2071,7 +2075,8 @@
Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
- packagePermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+ packagePermissions.get(key).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
pkgsWithPermissionsToHandle.remove(key);
}
} else {
@@ -2099,7 +2104,8 @@
proto.write(RankingHelperProto.RecordProto.PACKAGE, p.second);
proto.write(RankingHelperProto.RecordProto.UID, p.first);
proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
- packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+ packagePermissions.get(p).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
proto.end(fToken);
}
}
@@ -2110,7 +2116,7 @@
* Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
*/
public void pullPackagePreferencesStats(List<StatsEvent> events,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
if (pkgPermissions != null) {
pkgsWithPermissionsToHandle = pkgPermissions.keySet();
@@ -2134,7 +2140,8 @@
int importance = IMPORTANCE_NONE;
Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
- importance = pkgPermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
+ importance = pkgPermissions.get(key).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
pkgsWithPermissionsToHandle.remove(key);
}
event.writeInt(importance);
@@ -2158,7 +2165,7 @@
.setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
event.writeInt(p.first);
event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeInt(pkgPermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+ event.writeInt(pkgPermissions.get(p).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
// fill out the rest of the fields with default values so as not to confuse the
// builder
@@ -2236,7 +2243,7 @@
}
public JSONObject dumpJson(NotificationManagerService.DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
JSONObject ranking = new JSONObject();
JSONArray PackagePreferencess = new JSONArray();
try {
@@ -2266,7 +2273,7 @@
&& pkgsWithPermissionsToHandle.contains(key)) {
PackagePreferences.put("importance",
NotificationListenerService.Ranking.importanceToString(
- pkgPermissions.get(key)
+ pkgPermissions.get(key).first
? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
pkgsWithPermissionsToHandle.remove(key);
}
@@ -2316,7 +2323,7 @@
PackagePreferences.put("packageName", p.second);
PackagePreferences.put("importance",
NotificationListenerService.Ranking.importanceToString(
- pkgPermissions.get(p)
+ pkgPermissions.get(p).first
? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
} catch (JSONException e) {
// pass
@@ -2344,7 +2351,7 @@
* @return
*/
public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter,
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
JSONArray bans = new JSONArray();
Map<Integer, String> packageBans = mPermissionHelper.isMigrationEnabled()
? getPermissionBasedPackageBans(pkgPermissions) : getPackageBans();
@@ -2383,11 +2390,11 @@
// Same functionality as getPackageBans by extracting the set of packages from the provided
// map that are disallowed from sending notifications.
protected Map<Integer, String> getPermissionBasedPackageBans(
- ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
ArrayMap<Integer, String> packageBans = new ArrayMap<>();
if (pkgPermissions != null) {
for (Pair<Integer, String> p : pkgPermissions.keySet()) {
- if (!pkgPermissions.get(p)) {
+ if (!pkgPermissions.get(p).first) {
packageBans.put(p.first, p.second);
}
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 37cb8a9..fb77d10 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -1081,6 +1081,10 @@
} catch (RemoteException e) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"apexservice not available");
+ } catch (PackageManagerException e) {
+ // Catching it in order not to fall back to Exception which rethrows the
+ // PackageManagerException with a common error code.
+ throw e;
} catch (Exception e) {
// TODO(b/187864524): is INSTALL_FAILED_INTERNAL_ERROR is the right error code here?
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b605c93..9b21790 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2347,7 +2347,7 @@
@Override
public final boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
int userId, @PackageManager.ComponentInfoFlags long flags) {
- return filterStaticSharedLibPackage(ps, uid, userId, flags) && filterSdkLibPackage(ps, uid,
+ return filterStaticSharedLibPackage(ps, uid, userId, flags) || filterSdkLibPackage(ps, uid,
userId, flags);
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 641f24f..e8922eb 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -251,20 +251,29 @@
if (priorUserStates != null) {
synchronized (mPm.mLock) {
- for (int i = 0; i < allUsers.length; i++) {
- TempUserState priorUserState = priorUserStates.get(allUsers[i]);
- int enabledState = priorUserState.enabledState;
- PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
- pkgSetting.setEnabled(enabledState, allUsers[i],
- priorUserState.lastDisableAppCaller);
-
+ PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
+ if (pkgSetting != null) {
AndroidPackage aPkg = pkgSetting.getPkg();
boolean pkgEnabled = aPkg != null && aPkg.isEnabled();
- if (!reEnableStub && priorUserState.installed
- && ((enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
- || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
- reEnableStub = true;
+ for (int i = 0; i < allUsers.length; i++) {
+ TempUserState priorUserState = priorUserStates.get(allUsers[i]);
+ int enabledState = priorUserState.enabledState;
+ pkgSetting.setEnabled(enabledState, allUsers[i],
+ priorUserState.lastDisableAppCaller);
+ if (!reEnableStub && priorUserState.installed
+ && (
+ (enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
+ || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
+ reEnableStub = true;
+ }
}
+ } else {
+ // This should not happen. If priorUserStates != null, we are uninstalling
+ // an update of a system app. In that case, mPm.mSettings.getPackageLpr()
+ // should return a non-null value for the target packageName because
+ // restoreDisabledSystemPackageLIF() is called during deletePackageLIF().
+ Slog.w(TAG, "Missing PackageSetting after uninstalling the update for"
+ + " system app: " + packageName + ". This should not happen.");
}
mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
}
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 509702f..dfa6c66 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -126,13 +126,13 @@
return null;
}
- public OverlayConfig setUpSystemPackages(
+ public OverlayConfig initPackages(
WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
long startTime) {
PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
ExecutorService executorService = ParallelPackageParser.makeExecutorService();
- // Prepare apex package info before scanning APKs, these information are needed when
+ // Prepare apex package info before scanning APKs, this information is needed when
// scanning apk in apex.
mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
@@ -289,7 +289,7 @@
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
- mInstallPackageHelper.installSystemPackagesFromDir(scanDir, parseFlags, scanFlags,
+ mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
currentTime, packageParser, executorService);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d2087ee..80c2a7e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3720,7 +3720,7 @@
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- public void installSystemPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
+ public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 28204ea..55a0c96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -706,10 +706,9 @@
if (mCommitted.get()) {
mStagingManager.abortCommittedSession(this);
}
- destroy();
+ destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
- maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
- "Session was abandoned because the parent session is abandoned");
+ maybeCleanUpChildSessions();
};
if (mStageDirInUse) {
// Pre-reboot verification is ongoing, not safe to clean up the session yet.
@@ -2132,7 +2131,6 @@
destroy();
// Dispatch message to remove session from PackageInstallerService.
dispatchSessionFinished(error, msg, null);
- maybeFinishChildSessions(error, msg);
}
}
@@ -3648,19 +3646,20 @@
throw new SecurityException("Must be sealed to accept permissions");
}
- PackageInstallerSession root = hasParentSessionId()
- ? mSessionProvider.getSession(getParentSessionId()) : this;
-
if (accepted) {
// Mark and kick off another install pass
synchronized (mLock) {
mPermissionsManuallyAccepted = true;
}
+
+ PackageInstallerSession root =
+ (hasParentSessionId())
+ ? mSessionProvider.getSession(getParentSessionId())
+ : this;
root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
} else {
- root.destroy();
- root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
- root.maybeFinishChildSessions(INSTALL_FAILED_ABORTED, "User rejected permissions");
+ destroyInternal();
+ dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
}
}
@@ -3711,12 +3710,27 @@
}
/**
- * Calls dispatchSessionFinished() on all child sessions with the given error code and
- * error message to prevent orphaned child sessions.
+ * Cleans up the relevant stored files and information of all child sessions.
+ * <p>Cleaning up the stored files and session information is necessary for
+ * preventing the orphan children sessions.
+ * <ol>
+ * <li>To call {@link #destroyInternal()} cleans up the stored files.</li>
+ * <li>To call {@link #dispatchSessionFinished(int, String, Bundle)} to trigger the
+ * procedure to clean up the information in PackageInstallerService.</li>
+ * </ol></p>
*/
- private void maybeFinishChildSessions(int returnCode, String msg) {
- for (PackageInstallerSession child : getChildSessions()) {
- child.dispatchSessionFinished(returnCode, msg, null);
+ private void maybeCleanUpChildSessions() {
+ if (!isMultiPackage()) {
+ return;
+ }
+
+ final List<PackageInstallerSession> childSessions = getChildSessions();
+ final int size = childSessions.size();
+ for (int i = 0; i < size; ++i) {
+ final PackageInstallerSession session = childSessions.get(i);
+ session.destroyInternal();
+ session.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned"
+ + " because the parent session is abandoned", null);
}
}
@@ -3728,12 +3742,10 @@
if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
return;
}
- mDestroyed = true;
+ destroyInternal();
}
- destroy();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
- maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
- "Session was abandoned because the parent session is abandoned");
+ maybeCleanUpChildSessions();
}
private void assertNotChild(String cookie) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe5bce13..0c78ab8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1975,7 +1975,7 @@
mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
final int[] userIds = mUserManager.getUserIds();
- mOverlayConfig = mInitAndSystemPackageHelper.setUpSystemPackages(packageSettings,
+ mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
userIds, startTime);
// Resolve the storage manager.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index b89017ed..0564e85 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -878,6 +878,9 @@
if (userId == UserHandle.USER_ALL) {
getFlags |= PackageManager.MATCH_KNOWN_PACKAGES;
}
+ if (showSdks) {
+ getFlags |= PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
+ }
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
@SuppressWarnings("unchecked")
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 75f3725..041c4fe 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -431,6 +431,8 @@
}
}
}
+ boolean wasNonInternal = permission != null && permission.mType != TYPE_CONFIG
+ && !permission.isInternal();
boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
&& !permission.isRuntime();
if (permission == null) {
@@ -476,10 +478,10 @@
r.append("DUP:");
r.append(permissionInfo.name);
}
- if ((permission.isInternal() && ownerChanged)
+ if ((permission.isInternal() && (ownerChanged || wasNonInternal))
|| (permission.isRuntime() && (ownerChanged || wasNonRuntime))) {
// If this is an internal/runtime permission and the owner has changed, or this wasn't a
- // runtime permission, then permission state should be cleaned up.
+ // internal/runtime permission, then permission state should be cleaned up.
permission.mDefinitionChanged = true;
}
if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5c15a84..298f102 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -523,6 +523,7 @@
private boolean mPendingKeyguardOccluded;
private boolean mKeyguardOccludedChanged;
+ private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
Intent mHomeIntent;
Intent mCarDockIntent;
Intent mDeskDockIntent;
@@ -1852,6 +1853,9 @@
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
mLogger = new MetricsLogger();
+ mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
+ .createSleepTokenAcquirer("ScreenOff");
+
Resources res = mContext.getResources();
mWakeOnDpadKeyPress =
res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress);
@@ -4521,6 +4525,7 @@
if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
if (displayId == DEFAULT_DISPLAY) {
+ updateScreenOffSleepToken(true);
mRequestedOrSleepingDefaultDisplay = false;
mDefaultDisplayPolicy.screenTurnedOff();
synchronized (mLock) {
@@ -4553,6 +4558,7 @@
if (displayId == DEFAULT_DISPLAY) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
0 /* cookie */);
+ updateScreenOffSleepToken(false);
mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
mBootAnimationDismissable = false;
@@ -5073,6 +5079,15 @@
}
}
+ // TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
+ private void updateScreenOffSleepToken(boolean acquire) {
+ if (acquire) {
+ mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
+ } else {
+ mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
+ }
+ }
+
/** {@inheritDoc} */
@Override
public void enableScreenAfterBoot() {
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 7c0005c..f368698 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -16,14 +16,19 @@
package com.android.server.policy;
+import static android.hardware.fingerprint.FingerprintStateListener.STATE_BP_AUTH;
import static android.hardware.fingerprint.FingerprintStateListener.STATE_ENROLLING;
import static android.hardware.fingerprint.FingerprintStateListener.STATE_IDLE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -34,9 +39,11 @@
import android.view.WindowManager;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* Defines behavior for handling interactions between power button events and
@@ -44,68 +51,115 @@
* lives on the power button.
*/
public class SideFpsEventHandler {
+
+ private static final int DEBOUNCE_DELAY_MILLIS = 500;
+
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
- @NonNull private final AtomicBoolean mIsSideFps;
+ @NonNull private final Supplier<AlertDialog.Builder> mDialogSupplier;
@NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
+ @Nullable private Dialog mDialog;
+ @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener = (dialog) -> {
+ if (mDialog == dialog) {
+ mDialog = null;
+ }
+ };
+
private @FingerprintStateListener.State int mFingerprintState;
SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
+ this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
+ }
+
+ @VisibleForTesting
+ SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager,
+ Supplier<AlertDialog.Builder> dialogSupplier) {
mContext = context;
mHandler = handler;
mPowerManager = powerManager;
+ mDialogSupplier = dialogSupplier;
mFingerprintState = STATE_IDLE;
- mIsSideFps = new AtomicBoolean(false);
mSideFpsEventHandlerReady = new AtomicBoolean(false);
+
+ // ensure dialog is dismissed if screen goes off for unrelated reasons
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
/**
- * Called from {@link PhoneWindowManager} after power button is pressed. Checks fingerprint
- * sensor state and if mFingerprintState = STATE_ENROLLING, displays a dialog confirming intent
- * to turn screen off. If confirmed, the device goes to sleep, and if canceled, the dialog is
- * dismissed.
+ * Called from {@link PhoneWindowManager} after the power button is pressed and displays a
+ * dialog confirming the user's intent to turn screen off if a fingerprint operation is
+ * active. The device goes to sleep if confirmed otherwise the dialog is dismissed.
+ *
* @param eventTime powerPress event time
* @return true if powerPress was consumed, false otherwise
*/
public boolean onSinglePressDetected(long eventTime) {
- if (!mSideFpsEventHandlerReady.get() || !mIsSideFps.get()
- || mFingerprintState != STATE_ENROLLING) {
+ if (!mSideFpsEventHandlerReady.get()) {
return false;
}
- mHandler.post(() -> {
- Dialog confirmScreenOffDialog = new AlertDialog.Builder(mContext)
- .setTitle(R.string.fp_enrollment_powerbutton_intent_title)
- .setMessage(R.string.fp_enrollment_powerbutton_intent_message)
- .setPositiveButton(
- R.string.fp_enrollment_powerbutton_intent_positive_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- mPowerManager.goToSleep(
- eventTime,
- PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
- 0 /* flags */
- );
- }
- })
- .setNegativeButton(
- R.string.fp_enrollment_powerbutton_intent_negative_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .setCancelable(false)
- .create();
- confirmScreenOffDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- confirmScreenOffDialog.show();
- });
- return true;
+
+ switch (mFingerprintState) {
+ case STATE_ENROLLING:
+ case STATE_BP_AUTH:
+ mHandler.post(() -> {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ mDialog = showConfirmDialog(mDialogSupplier.get(),
+ mPowerManager, eventTime, mFingerprintState, mDialogDismissListener);
+ });
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @NonNull
+ private static Dialog showConfirmDialog(@NonNull AlertDialog.Builder dialogBuilder,
+ @NonNull PowerManager powerManager, long eventTime,
+ @FingerprintStateListener.State int fingerprintState,
+ @NonNull DialogInterface.OnDismissListener dismissListener) {
+ final boolean enrolling = fingerprintState == STATE_ENROLLING;
+ final int title = enrolling ? R.string.fp_power_button_enrollment_title
+ : R.string.fp_power_button_bp_title;
+ final int message = enrolling ? R.string.fp_power_button_enrollment_message
+ : R.string.fp_power_button_bp_message;
+ final int positiveText = enrolling ? R.string.fp_power_button_enrollment_positive_button
+ : R.string.fp_power_button_bp_positive_button;
+ final int negativeText = enrolling ? R.string.fp_power_button_enrollment_negative_button
+ : R.string.fp_power_button_bp_negative_button;
+
+ final Dialog confirmScreenOffDialog = dialogBuilder
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(positiveText,
+ (dialog, which) -> {
+ dialog.dismiss();
+ powerManager.goToSleep(
+ eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ 0 /* flags */
+ );
+ })
+ .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
+ .setOnDismissListener(dismissListener)
+ .setCancelable(false)
+ .create();
+ confirmScreenOffDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ confirmScreenOffDialog.show();
+
+ return confirmScreenOffDialog;
}
/**
@@ -116,26 +170,44 @@
*/
public void onFingerprintSensorReady() {
final PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) return;
- FingerprintManager fingerprintManager =
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return;
+ }
+
+ final FingerprintManager fingerprintManager =
mContext.getSystemService(FingerprintManager.class);
fingerprintManager.addAuthenticatorsRegisteredCallback(
new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@Override
public void onAllAuthenticatorsRegistered(
List<FingerprintSensorPropertiesInternal> sensors) {
- mIsSideFps.set(fingerprintManager.isPowerbuttonFps());
- FingerprintStateListener fingerprintStateListener =
- new FingerprintStateListener() {
- @Override
- public void onStateChanged(
- @FingerprintStateListener.State int newState) {
- mFingerprintState = newState;
- }
- };
- fingerprintManager.registerFingerprintStateListener(
- fingerprintStateListener);
- mSideFpsEventHandlerReady.set(true);
+ if (fingerprintManager.isPowerbuttonFps()) {
+ fingerprintManager.registerFingerprintStateListener(
+ new FingerprintStateListener() {
+ @Nullable private Runnable mStateRunnable = null;
+
+ @Override
+ public void onStateChanged(
+ @FingerprintStateListener.State int newState) {
+ if (mStateRunnable != null) {
+ mHandler.removeCallbacks(mStateRunnable);
+ mStateRunnable = null;
+ }
+
+ // When the user hits the power button the events can
+ // arrive in any order (success auth & power). Add a
+ // damper when moving to idle in case auth is first
+ if (newState == STATE_IDLE) {
+ mStateRunnable = () -> mFingerprintState = newState;
+ mHandler.postDelayed(mStateRunnable,
+ DEBOUNCE_DELAY_MILLIS);
+ } else {
+ mFingerprintState = newState;
+ }
+ }
+ });
+ mSideFpsEventHandlerReady.set(true);
+ }
}
});
}
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index d24a3df..cf0e350 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -66,6 +66,7 @@
KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+ KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
})
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.SOURCE)
@@ -156,12 +157,18 @@
"time_detector_origin_priorities_override";
/**
- * The key to override the time detector lower bound configuration. The values is the number of
+ * The key to override the time detector lower bound configuration. The value is the number of
* milliseconds since the beginning of the Unix epoch.
*/
public static final @DeviceConfigKey String KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE =
"time_detector_lower_bound_millis_override";
+ /**
+ * The key to allow extra metrics / telemetry information to be collected from internal testers.
+ */
+ public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED =
+ "enhanced_metrics_collection_enabled";
+
@GuardedBy("mListeners")
private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 65f077e..2291777 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -40,6 +40,7 @@
private final boolean mTelephonyDetectionSupported;
private final boolean mGeoDetectionSupported;
private final boolean mTelephonyFallbackSupported;
+ private final boolean mEnhancedMetricsCollectionEnabled;
private final boolean mAutoDetectionEnabledSetting;
private final @UserIdInt int mUserId;
private final boolean mUserConfigAllowed;
@@ -50,6 +51,7 @@
mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
mGeoDetectionSupported = builder.mGeoDetectionSupported;
mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
+ mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
mUserId = builder.mUserId;
@@ -81,6 +83,15 @@
return mTelephonyFallbackSupported;
}
+ /**
+ * Returns {@code true} if the device can collect / report extra metrics information for QA
+ * / testers. These metrics might involve logging more expensive or more revealing data that
+ * would not be collected from the set of public users.
+ */
+ public boolean isEnhancedMetricsCollectionEnabled() {
+ return mEnhancedMetricsCollectionEnabled;
+ }
+
/** Returns the value of the auto time zone detection enabled setting. */
public boolean getAutoDetectionEnabledSetting() {
return mAutoDetectionEnabledSetting;
@@ -227,6 +238,7 @@
&& mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
&& mGeoDetectionSupported == that.mGeoDetectionSupported
&& mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
+ && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled
&& mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
&& mLocationEnabledSetting == that.mLocationEnabledSetting
&& mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
@@ -235,7 +247,8 @@
@Override
public int hashCode() {
return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
- mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
+ mGeoDetectionSupported, mTelephonyFallbackSupported,
+ mEnhancedMetricsCollectionEnabled, mAutoDetectionEnabledSetting,
mLocationEnabledSetting, mGeoDetectionEnabledSetting);
}
@@ -247,6 +260,7 @@
+ ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
+ ", mGeoDetectionSupported=" + mGeoDetectionSupported
+ ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
+ + ", mEnhancedMetricsCollectionEnabled=" + mEnhancedMetricsCollectionEnabled
+ ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
+ ", mLocationEnabledSetting=" + mLocationEnabledSetting
+ ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
@@ -264,6 +278,7 @@
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
private boolean mTelephonyFallbackSupported;
+ private boolean mEnhancedMetricsCollectionEnabled;
private boolean mAutoDetectionEnabledSetting;
private boolean mLocationEnabledSetting;
private boolean mGeoDetectionEnabledSetting;
@@ -284,6 +299,7 @@
this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
+ this.mEnhancedMetricsCollectionEnabled = toCopy.mEnhancedMetricsCollectionEnabled;
this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
@@ -323,6 +339,14 @@
}
/**
+ * Sets the value for enhanced metrics collection.
+ */
+ public Builder setEnhancedMetricsCollectionEnabled(boolean enabled) {
+ mEnhancedMetricsCollectionEnabled = enabled;
+ return this;
+ }
+
+ /**
* Sets the value of the automatic time zone detection enabled setting for this device.
*/
public Builder setAutoDetectionEnabledSetting(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index f156f8c..ecac267 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -34,11 +34,13 @@
* A class that provides time zone detector state information for metrics.
*
* <p>
- * Regarding time zone ID ordinals:
+ * Regarding the use of time zone ID ordinals in metrics / telemetry:
* <p>
- * We don't want to leak user location information by reporting time zone IDs. Instead, time zone
- * IDs are consistently identified within a given instance of this class by a numeric ID. This
- * allows comparison of IDs without revealing what those IDs are.
+ * For general metrics, we don't want to leak user location information by reporting time zone
+ * IDs. Instead, time zone IDs are consistently identified within a given instance of this class by
+ * a numeric ID (ordinal). This allows comparison of IDs without revealing what those IDs are.
+ * See {@link #isEnhancedMetricsCollectionEnabled()} for the setting that enables actual IDs to be
+ * collected.
*/
public final class MetricsTimeZoneDetectorState {
@@ -54,6 +56,7 @@
@NonNull private final ConfigurationInternal mConfigurationInternal;
private final int mDeviceTimeZoneIdOrdinal;
+ @Nullable private final String mDeviceTimeZoneId;
@Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
@Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
@Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
@@ -61,11 +64,13 @@
private MetricsTimeZoneDetectorState(
@NonNull ConfigurationInternal configurationInternal,
int deviceTimeZoneIdOrdinal,
+ @Nullable String deviceTimeZoneId,
@Nullable MetricsTimeZoneSuggestion latestManualSuggestion,
@Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion,
@Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) {
mConfigurationInternal = Objects.requireNonNull(configurationInternal);
mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal;
+ mDeviceTimeZoneId = deviceTimeZoneId;
mLatestManualSuggestion = latestManualSuggestion;
mLatestTelephonySuggestion = latestTelephonySuggestion;
mLatestGeolocationSuggestion = latestGeolocationSuggestion;
@@ -83,18 +88,24 @@
@Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
@Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
+ boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
+ String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
int deviceTimeZoneIdOrdinal =
tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId));
MetricsTimeZoneSuggestion latestCanonicalManualSuggestion =
- createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion);
+ createMetricsTimeZoneSuggestion(
+ tzIdOrdinalGenerator, latestManualSuggestion, includeZoneIds);
MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
- createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion);
+ createMetricsTimeZoneSuggestion(
+ tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
- createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion);
+ createMetricsTimeZoneSuggestion(
+ tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
return new MetricsTimeZoneDetectorState(
- configurationInternal, deviceTimeZoneIdOrdinal, latestCanonicalManualSuggestion,
- latestCanonicalTelephonySuggestion, latestCanonicalGeolocationSuggestion);
+ configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
+ latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion,
+ latestCanonicalGeolocationSuggestion);
}
/** Returns true if the device supports telephony time zone detection. */
@@ -112,6 +123,11 @@
return mConfigurationInternal.isTelephonyFallbackSupported();
}
+ /** Returns true if enhanced metric collection is enabled. */
+ public boolean isEnhancedMetricsCollectionEnabled() {
+ return mConfigurationInternal.isEnhancedMetricsCollectionEnabled();
+ }
+
/** Returns true if user's location can be used generally. */
public boolean getUserLocationEnabledSetting() {
return mConfigurationInternal.getLocationEnabledSetting();
@@ -142,7 +158,7 @@
}
/**
- * Returns the ordinal for the device's currently set time zone ID.
+ * Returns the ordinal for the device's current time zone ID.
* See {@link MetricsTimeZoneDetectorState} for information about ordinals.
*/
public int getDeviceTimeZoneIdOrdinal() {
@@ -150,6 +166,16 @@
}
/**
+ * Returns the device's current time zone ID. This will only be populated if {@link
+ * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+ * MetricsTimeZoneDetectorState} for details.
+ */
+ @Nullable
+ public String getDeviceTimeZoneId() {
+ return mDeviceTimeZoneId;
+ }
+
+ /**
* Returns a canonical form of the last manual suggestion received.
*/
@Nullable
@@ -183,6 +209,7 @@
}
MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o;
return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal
+ && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId)
&& mConfigurationInternal.equals(that.mConfigurationInternal)
&& Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion)
&& Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion)
@@ -191,7 +218,7 @@
@Override
public int hashCode() {
- return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal,
+ return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId,
mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion);
}
@@ -200,6 +227,7 @@
return "MetricsTimeZoneDetectorState{"
+ "mConfigurationInternal=" + mConfigurationInternal
+ ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal
+ + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId
+ ", mLatestManualSuggestion=" + mLatestManualSuggestion
+ ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion
+ ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion
@@ -209,34 +237,40 @@
@Nullable
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
- @NonNull ManualTimeZoneSuggestion manualSuggestion) {
+ @NonNull ManualTimeZoneSuggestion manualSuggestion,
+ boolean includeFullZoneIds) {
if (manualSuggestion == null) {
return null;
}
- int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId());
- return MetricsTimeZoneSuggestion.createCertain(
- new int[] { zoneIdOrdinal });
+ String suggestionZoneId = manualSuggestion.getZoneId();
+ String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+ int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+ return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
}
@Nullable
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
- @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) {
+ @NonNull TelephonyTimeZoneSuggestion telephonySuggestion,
+ boolean includeFullZoneIds) {
if (telephonySuggestion == null) {
return null;
}
- if (telephonySuggestion.getZoneId() == null) {
+ String suggestionZoneId = telephonySuggestion.getZoneId();
+ if (suggestionZoneId == null) {
return MetricsTimeZoneSuggestion.createUncertain();
}
- int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId());
- return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal });
+ String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+ int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+ return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
}
@Nullable
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
- @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) {
+ @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion,
+ boolean includeFullZoneIds) {
if (geolocationSuggestion == null) {
return null;
}
@@ -245,7 +279,9 @@
if (zoneIds == null) {
return MetricsTimeZoneSuggestion.createUncertain();
}
- return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds));
+ String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null;
+ int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds);
+ return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
}
/**
@@ -254,33 +290,49 @@
* MetricsTimeZoneSuggestion proto definition.
*/
public static final class MetricsTimeZoneSuggestion {
- @Nullable
- private final int[] mZoneIdOrdinals;
+ @Nullable private final String[] mZoneIds;
+ @Nullable private final int[] mZoneIdOrdinals;
- MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) {
+ private MetricsTimeZoneSuggestion(
+ @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) {
+ mZoneIds = zoneIds;
mZoneIdOrdinals = zoneIdOrdinals;
}
@NonNull
static MetricsTimeZoneSuggestion createUncertain() {
- return new MetricsTimeZoneSuggestion(null);
+ return new MetricsTimeZoneSuggestion(null, null);
}
@NonNull
static MetricsTimeZoneSuggestion createCertain(
- @NonNull int[] zoneIdOrdinals) {
- return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
+ @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) {
+ return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals);
}
public boolean isCertain() {
return mZoneIdOrdinals != null;
}
+ /**
+ * Returns ordinals for the time zone IDs contained in the suggestion.
+ * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
+ */
@Nullable
public int[] getZoneIdOrdinals() {
return mZoneIdOrdinals;
}
+ /**
+ * Returns the time zone IDs contained in the suggestion. This will only be populated if
+ * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+ * MetricsTimeZoneDetectorState} for details.
+ */
+ @Nullable
+ public String[] getZoneIds() {
+ return mZoneIds;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -290,18 +342,22 @@
return false;
}
MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o;
- return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals);
+ return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals)
+ && Arrays.equals(mZoneIds, that.mZoneIds);
}
@Override
public int hashCode() {
- return Arrays.hashCode(mZoneIdOrdinals);
+ int result = Arrays.hashCode(mZoneIds);
+ result = 31 * result + Arrays.hashCode(mZoneIdOrdinals);
+ return result;
}
@Override
public String toString() {
return "MetricsTimeZoneSuggestion{"
+ "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals)
+ + ", mZoneIds=" + Arrays.toString(mZoneIds)
+ '}';
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 02ea433..4612f65 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -62,6 +62,7 @@
private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH =
Collections.unmodifiableSet(new ArraySet<>(new String[] {
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+ ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
@@ -296,6 +297,7 @@
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
.setTelephonyFallbackSupported(isTelephonyFallbackSupported())
+ .setEnhancedMetricsCollectionEnabled(isEnhancedMetricsCollectionEnabled())
.setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
.setUserConfigAllowed(isUserConfigAllowed(userId))
.setLocationEnabledSetting(getLocationEnabledSetting(userId))
@@ -400,6 +402,17 @@
defaultEnabled);
}
+ /**
+ * Returns {@code true} if extra metrics / telemetry information can be collected. Used for
+ * internal testers.
+ */
+ private boolean isEnhancedMetricsCollectionEnabled() {
+ final boolean defaultEnabled = false;
+ return mServerFlags.getBoolean(
+ ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+ defaultEnabled);
+ }
+
@Override
@NonNull
public synchronized String getPrimaryLocationTimeZoneProviderPackageName() {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 14784cf..f75608e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -364,6 +364,13 @@
}
}
+ @NonNull
+ MetricsTimeZoneDetectorState generateMetricsState() {
+ enforceManageTimeZoneDetectorPermission();
+
+ return mTimeZoneDetectorStrategy.generateMetricsState();
+ }
+
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 2b912ad..8535b3d 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,6 +15,7 @@
*/
package com.android.server.timezonedetector;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
@@ -28,6 +29,7 @@
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
+import static com.android.server.timedetector.ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED;
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED;
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT;
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE;
@@ -80,6 +82,8 @@
return runSuggestTelephonyTimeZone();
case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
return runEnableTelephonyFallback();
+ case SHELL_COMMAND_DUMP_METRICS:
+ return runDumpMetrics();
default: {
return handleDefaultCommands(cmd);
}
@@ -168,14 +172,22 @@
pw.println("Suggestion " + suggestion + " injected.");
return 0;
} catch (RuntimeException e) {
- pw.println(e.toString());
+ pw.println(e);
return 1;
}
}
private int runEnableTelephonyFallback() {
mInterface.enableTelephonyFallback();
- return 1;
+ return 0;
+ }
+
+ private int runDumpMetrics() {
+ final PrintWriter pw = getOutPrintWriter();
+ MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
+ pw.println("MetricsTimeZoneDetectorState:");
+ pw.println(metricsState.toString());
+ return 0;
}
@Override
@@ -208,10 +220,10 @@
pw.println();
pw.printf(" %s <geolocation suggestion opts>\n",
SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
- pw.printf(" %s <manual suggestion opts>\n",
- SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
- pw.printf(" %s <telephony suggestion opts>\n",
- SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+ pw.printf(" %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
+ pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+ pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS);
+ pw.printf(" Dumps the service metrics to stdout for inspection.\n");
pw.println();
GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
@@ -235,6 +247,8 @@
pw.printf(" %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED);
pw.printf(" Used to enable / disable support for telephony detection fallback. Also see"
+ " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
+ pw.printf(" %s\n", KEY_ENHANCED_METRICS_COLLECTION_ENABLED);
+ pw.printf(" Used to increase the detail of metrics collected / reported.\n");
pw.println();
pw.printf("[*] To be enabled, the user must still have location = on / auto time zone"
+ " detection = on.\n");
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 59b6a08..7e36f89 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -44,6 +44,7 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.PlaybackParams;
+import android.media.tv.AitInfo;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.DvbDeviceInfo;
@@ -1761,6 +1762,26 @@
}
@Override
+ public void setIAppNotificationEnabled(IBinder sessionToken, boolean enabled, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setIAppNotificationEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .setIAppNotificationEnabled(enabled);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in setIAppNotificationEnabled", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
int userId) {
final int callingUid = Binder.getCallingUid();
@@ -3341,7 +3362,23 @@
}
}
- // For the recording session only
+ @Override
+ public void onAitInfoUpdated(AitInfo aitInfo) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onAitInfoUpdated(" + aitInfo + ")");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onAitInfoUpdated(aitInfo, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onAitInfoUpdated", e);
+ }
+ }
+ }
+
@Override
public void onTuned(Uri channelUri) {
synchronized (mLock) {
@@ -3352,7 +3389,7 @@
return;
}
try {
- mSessionState.client.onTuned(mSessionState.seq, channelUri);
+ mSessionState.client.onTuned(channelUri, mSessionState.seq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onTuned", e);
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index c7b6421..f12139d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -28,6 +28,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.graphics.Rect;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.interactive.ITvIAppClient;
@@ -39,6 +40,7 @@
import android.media.tv.interactive.ITvIAppSessionCallback;
import android.media.tv.interactive.TvIAppInfo;
import android.media.tv.interactive.TvIAppService;
+import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
@@ -522,11 +524,6 @@
removeSessionStateLocked(state.mSessionToken, state.mUserId);
}
- private SessionState getSessionState(IBinder sessionToken) {
- // TODO: implement user state and get session from it.
- return null;
- }
-
private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
String methodName) {
return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
@@ -570,6 +567,11 @@
}
@GuardedBy("mLock")
+ private ITvIAppSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
+ }
+
+ @GuardedBy("mLock")
private ITvIAppSession getSessionLocked(SessionState sessionState) {
ITvIAppSession session = sessionState.mSession;
if (session == null) {
@@ -601,6 +603,32 @@
}
@Override
+ public void prepare(String tiasId, int type, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "prepare");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+ if (iAppState == null) {
+ Slogf.e(TAG, "failed to prepare TIAS - unknown TIAS id " + tiasId);
+ return;
+ }
+ ComponentName componentName = iAppState.mInfo.getComponent();
+ ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+ if (serviceState != null) {
+ serviceState.mService.prepare(type);
+ }
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in prepare", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
int seq, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -683,17 +711,53 @@
}
@Override
+ public void notifyTuned(IBinder sessionToken, Uri channelUri, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTuned(sessionToken=" + sessionToken
+ + ", Uri=" + channelUri + ")");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "notifyTuned");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyTuned(channelUri);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTuned", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startIApp(IBinder sessionToken, int userId) {
if (DEBUG) {
Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
}
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "notifyTuned");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
try {
- SessionState sessionState = getSessionState(sessionToken);
- if (sessionState != null && sessionState.mSession != null) {
- sessionState.mSession.startIApp();
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).startIApp();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in start", e);
+ }
}
- } catch (RemoteException e) {
- Slogf.e(TAG, "error in start", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@@ -802,6 +866,67 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createMediaView(windowToken, frame);
+ } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ Slog.e(TAG, "error in createMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutMediaView(frame);
+ } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ Slog.e(TAG, "error in relayoutMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeMediaView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeMediaView();
+ } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ Slog.e(TAG, "error in removeMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
index 5c1b5ff..1c675c2 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
@@ -46,6 +46,7 @@
private static final String PARCEL_UUID_KEY = "PARCEL_UUID";
private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY";
private static final String INTEGER_KEY = "INTEGER_KEY";
+ private static final String STRING_KEY = "STRING_KEY";
/**
* Functional interface to convert an object of the specified type to a PersistableBundle.
@@ -91,6 +92,21 @@
return bundle.getInt(INTEGER_KEY);
};
+ /** Serializer to convert s String to a PersistableBundle. */
+ public static final Serializer<String> STRING_SERIALIZER =
+ (i) -> {
+ final PersistableBundle result = new PersistableBundle();
+ result.putString(STRING_KEY, i);
+ return result;
+ };
+
+ /** Deserializer to convert a PersistableBundle to a String. */
+ public static final Deserializer<String> STRING_DESERIALIZER =
+ (bundle) -> {
+ Objects.requireNonNull(bundle, "PersistableBundle is null");
+ return bundle.getString(STRING_KEY);
+ };
+
/**
* Converts a ParcelUuid to a PersistableBundle.
*
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index ddac9cd..1d6e158 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -53,13 +53,13 @@
IGNORED,
IGNORED_APP_OPS,
IGNORED_BACKGROUND,
- IGNORED_RINGTONE,
IGNORED_UNKNOWN_VIBRATION,
IGNORED_UNSUPPORTED,
IGNORED_FOR_ALARM,
IGNORED_FOR_EXTERNAL,
IGNORED_FOR_ONGOING,
IGNORED_FOR_POWER,
+ IGNORED_FOR_RINGER_MODE,
IGNORED_FOR_SETTINGS,
IGNORED_SUPERSEDED,
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index f82f99d..1ee115d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -53,12 +53,44 @@
import com.android.server.LocalServices;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/** Controls all the system settings related to vibration. */
final class VibrationSettings {
private static final String TAG = "VibrationSettings";
+ /**
+ * Set of usages allowed for vibrations from background processes.
+ *
+ * <p>Some examples are notification, ringtone or alarm vibrations, that are allowed to vibrate
+ * unexpectedly as they are meant to grab the user's attention. Hardware feedback and physical
+ * emulation are also supported, as the trigger process might still be in the background when
+ * the user interaction wakes the device.
+ */
+ private static final Set<Integer> BACKGROUND_PROCESS_USAGE_ALLOWLIST = new HashSet<>(
+ Arrays.asList(
+ USAGE_RINGTONE,
+ USAGE_ALARM,
+ USAGE_NOTIFICATION,
+ USAGE_COMMUNICATION_REQUEST,
+ USAGE_HARDWARE_FEEDBACK,
+ USAGE_PHYSICAL_EMULATION));
+
+ /**
+ * Set of usages allowed for vibrations in battery saver mode (low power).
+ *
+ * <p>Some examples are ringtone or alarm vibrations, that have high priority and should vibrate
+ * even when the device is saving battery.
+ */
+ private static final Set<Integer> BATTERY_SAVER_USAGE_ALLOWLIST = new HashSet<>(
+ Arrays.asList(
+ USAGE_RINGTONE,
+ USAGE_ALARM,
+ USAGE_COMMUNICATION_REQUEST));
+
/** Listener for changes on vibration settings. */
interface OnVibratorSettingsChanged {
/** Callback triggered when any of the vibrator settings change. */
@@ -94,8 +126,6 @@
@GuardedBy("mLock")
private boolean mApplyRampingRinger;
@GuardedBy("mLock")
- private int mZenMode;
- @GuardedBy("mLock")
private int mHapticFeedbackIntensity;
@GuardedBy("mLock")
private int mHardwareFeedbackIntensity;
@@ -104,7 +134,7 @@
@GuardedBy("mLock")
private int mRingIntensity;
@GuardedBy("mLock")
- private boolean mLowPowerMode;
+ private boolean mBatterySaverMode;
VibrationSettings(Context context, Handler handler) {
this(context, handler,
@@ -172,8 +202,8 @@
public void onLowPowerModeChanged(PowerSaveState result) {
boolean shouldNotifyListeners;
synchronized (mLock) {
- shouldNotifyListeners = result.batterySaverEnabled != mLowPowerMode;
- mLowPowerMode = result.batterySaverEnabled;
+ shouldNotifyListeners = result.batterySaverEnabled != mBatterySaverMode;
+ mBatterySaverMode = result.batterySaverEnabled;
}
if (shouldNotifyListeners) {
notifyListeners();
@@ -187,7 +217,6 @@
registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING));
registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER));
- registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE));
registerSettingsObserver(
Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY));
registerSettingsObserver(
@@ -299,71 +328,78 @@
return mFallbackEffects.get(effectId);
}
- /**
- * Return {@code true} if the device should vibrate for current ringer mode.
- *
- * <p>This checks the current {@link AudioManager#getRingerModeInternal()} against user settings
- * for ringtone usage only. All other usages are allowed independently of ringer mode.
- */
- public boolean shouldVibrateForRingerMode(int usageHint) {
- if (usageHint != USAGE_RINGTONE) {
- return true;
- }
- synchronized (mLock) {
- if (mAudioManager == null) {
- return false;
- }
- int ringerMode = mAudioManager.getRingerModeInternal();
- if (mVibrateWhenRinging) {
- return ringerMode != AudioManager.RINGER_MODE_SILENT;
- } else if (mApplyRampingRinger) {
- return ringerMode != AudioManager.RINGER_MODE_SILENT;
- } else {
- return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
- }
- }
- }
-
- /**
- * Returns {@code true} if this vibration is allowed for given {@code uid}.
- *
- * <p>This checks if the user is aware of this foreground process, or if the vibration usage is
- * allowed to play in the background (i.e. it's a notification, ringtone or alarm vibration).
- */
- public boolean shouldVibrateForUid(int uid, int usageHint) {
- return mUidObserver.isUidForeground(uid) || isClassAlarm(usageHint);
- }
-
- /**
- * Returns {@code true} if this vibration is allowed for current power mode state.
- *
- * <p>This checks if the device is in battery saver mode, in which case only alarm, ringtone and
- * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate.
- */
- public boolean shouldVibrateForPowerMode(int usageHint) {
- synchronized (mLock) {
- return !mLowPowerMode || usageHint == USAGE_RINGTONE || usageHint == USAGE_ALARM
- || usageHint == USAGE_COMMUNICATION_REQUEST;
- }
- }
-
/** Return {@code true} if input devices should vibrate instead of this device. */
public boolean shouldVibrateInputDevices() {
return mVibrateInputDevices;
}
- /** Return {@code true} if setting for {@link Settings.Global#ZEN_MODE} is not OFF. */
- public boolean isInZenMode() {
- return mZenMode != Settings.Global.ZEN_MODE_OFF;
+ /**
+ * Check if given vibration should be ignored by the service.
+ *
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored,
+ * null otherwise.
+ */
+ @Nullable
+ public Vibration.Status shouldIgnoreVibration(int uid, VibrationAttributes attrs) {
+ final int usage = attrs.getUsage();
+ synchronized (mLock) {
+ if (!mUidObserver.isUidForeground(uid)
+ && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
+ return Vibration.Status.IGNORED_BACKGROUND;
+ }
+
+ if (mBatterySaverMode && !BATTERY_SAVER_USAGE_ALLOWLIST.contains(usage)) {
+ return Vibration.Status.IGNORED_FOR_POWER;
+ }
+
+ int intensity = getCurrentIntensity(usage);
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ return Vibration.Status.IGNORED_FOR_SETTINGS;
+ }
+
+ if (!shouldVibrateForRingerModeLocked(usage)) {
+ return Vibration.Status.IGNORED_FOR_RINGER_MODE;
+ }
+ }
+ return null;
}
- private static boolean isClassAlarm(int usageHint) {
- return (usageHint & VibrationAttributes.USAGE_CLASS_MASK)
- == VibrationAttributes.USAGE_CLASS_ALARM;
+ /**
+ * Return {@code true} if the device should vibrate for current ringer mode.
+ *
+ * <p>This checks the current {@link AudioManager#getRingerModeInternal()} against user settings
+ * for touch and ringtone usages only. All other usages are allowed by this method.
+ */
+ @GuardedBy("mLock")
+ private boolean shouldVibrateForRingerModeLocked(int usageHint) {
+ // If audio manager was not loaded yet then assume most restrictive mode.
+ int ringerMode = (mAudioManager == null)
+ ? AudioManager.RINGER_MODE_SILENT
+ : mAudioManager.getRingerModeInternal();
+
+ switch (usageHint) {
+ case USAGE_TOUCH:
+ // Touch feedback disabled when phone is on silent mode.
+ return ringerMode != AudioManager.RINGER_MODE_SILENT;
+ case USAGE_RINGTONE:
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT:
+ return false;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ return true;
+ default:
+ // Ringtone vibrations also depend on 2 other settings:
+ return mVibrateWhenRinging || mApplyRampingRinger;
+ }
+ default:
+ // All other usages ignore ringer mode settings.
+ return true;
+ }
}
/** Updates all vibration settings and triggers registered listeners. */
- public void updateSettings() {
+ @VisibleForTesting
+ void updateSettings() {
synchronized (mLock) {
mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
mApplyRampingRinger = getSystemSetting(Settings.System.APPLY_RAMPING_RINGER, 0) != 0;
@@ -378,7 +414,6 @@
mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
getDefaultIntensity(USAGE_RINGTONE));
mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
- mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
}
notifyListeners();
}
@@ -399,31 +434,33 @@
@Override
public String toString() {
- return "VibrationSettings{"
- + "mVibrateInputDevices=" + mVibrateInputDevices
- + ", mVibrateWhenRinging=" + mVibrateWhenRinging
- + ", mApplyRampingRinger=" + mApplyRampingRinger
- + ", mLowPowerMode=" + mLowPowerMode
- + ", mZenMode=" + Settings.Global.zenModeToString(mZenMode)
- + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
- + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude()
- + ", mRampStepDuration=" + mRampStepDuration
- + ", mRampDownDuration=" + mRampDownDuration
- + ", mHardwareHapticFeedbackIntensity="
- + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
- + ", mHapticFeedbackIntensity="
- + intensityToString(getCurrentIntensity(USAGE_TOUCH))
- + ", mHapticFeedbackDefaultIntensity="
- + intensityToString(getDefaultIntensity(USAGE_TOUCH))
- + ", mNotificationIntensity="
- + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
- + ", mNotificationDefaultIntensity="
- + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
- + ", mRingIntensity="
- + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
- + ", mRingDefaultIntensity="
- + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
- + '}';
+ synchronized (mLock) {
+ return "VibrationSettings{"
+ + "mVibrateInputDevices=" + mVibrateInputDevices
+ + ", mVibrateWhenRinging=" + mVibrateWhenRinging
+ + ", mApplyRampingRinger=" + mApplyRampingRinger
+ + ", mBatterySaverMode=" + mBatterySaverMode
+ + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
+ + ", mHapticChannelMaxVibrationAmplitude="
+ + getHapticChannelMaxVibrationAmplitude()
+ + ", mRampStepDuration=" + mRampStepDuration
+ + ", mRampDownDuration=" + mRampDownDuration
+ + ", mHardwareHapticFeedbackIntensity="
+ + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
+ + ", mHapticFeedbackIntensity="
+ + intensityToString(getCurrentIntensity(USAGE_TOUCH))
+ + ", mHapticFeedbackDefaultIntensity="
+ + intensityToString(getDefaultIntensity(USAGE_TOUCH))
+ + ", mNotificationIntensity="
+ + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
+ + ", mNotificationDefaultIntensity="
+ + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
+ + ", mRingIntensity="
+ + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
+ + ", mRingDefaultIntensity="
+ + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
+ + '}';
+ }
}
/** Write current settings into given {@link ProtoOutputStream}. */
@@ -480,10 +517,6 @@
settingName, defaultValue, UserHandle.USER_CURRENT);
}
- private int getGlobalSetting(String settingName, int defaultValue) {
- return Settings.Global.getInt(mContext.getContentResolver(), settingName, defaultValue);
- }
-
private void registerSettingsObserver(Uri settingUri) {
mContext.getContentResolver().registerContentObserver(
settingUri, /* notifyForDescendants= */ true, mSettingObserver,
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 9717201..478e86e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -46,7 +46,6 @@
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
@@ -394,13 +393,13 @@
if (DEBUG) {
Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
}
- Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
- if (ignoreStatus != null) {
- endVibrationLocked(vib, ignoreStatus);
- return vib;
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+ vib.uid, vib.opPkg, vib.attrs);
+
+ if (ignoreStatus == null) {
+ ignoreStatus = shouldIgnoreVibrationForOngoingLocked(vib);
}
- ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib);
if (ignoreStatus != null) {
endVibrationLocked(vib, ignoreStatus);
return vib;
@@ -453,8 +452,7 @@
&& shouldCancelVibration(
mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
usageFilter)) {
- mCurrentExternalVibration.end(Vibration.Status.CANCELLED);
- mVibratorManagerRecords.record(mCurrentExternalVibration);
+ endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
mCurrentExternalVibration.externalVibration.mute();
mCurrentExternalVibration = null;
setExternalControl(false);
@@ -482,15 +480,76 @@
}
try {
if (isDumpProto) {
- mVibratorManagerRecords.dumpProto(fd);
+ dumpProto(fd);
} else {
- mVibratorManagerRecords.dumpText(pw);
+ dumpText(pw);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ private void dumpText(PrintWriter pw) {
+ if (DEBUG) {
+ Slog.d(TAG, "Dumping vibrator manager service to text...");
+ }
+ synchronized (mLock) {
+ pw.println("Vibrator Manager Service:");
+ pw.println(" mVibrationSettings:");
+ pw.println(" " + mVibrationSettings);
+ pw.println();
+ pw.println(" mVibratorControllers:");
+ for (int i = 0; i < mVibrators.size(); i++) {
+ pw.println(" " + mVibrators.valueAt(i));
+ }
+ pw.println();
+ pw.println(" mCurrentVibration:");
+ pw.println(" " + (mCurrentVibration == null
+ ? null : mCurrentVibration.getVibration().getDebugInfo()));
+ pw.println();
+ pw.println(" mNextVibration:");
+ pw.println(" " + (mNextVibration == null
+ ? null : mNextVibration.getVibration().getDebugInfo()));
+ pw.println();
+ pw.println(" mCurrentExternalVibration:");
+ pw.println(" " + (mCurrentExternalVibration == null
+ ? null : mCurrentExternalVibration.getDebugInfo()));
+ pw.println();
+ }
+ mVibratorManagerRecords.dumpText(pw);
+ }
+
+ synchronized void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ if (DEBUG) {
+ Slog.d(TAG, "Dumping vibrator manager service to proto...");
+ }
+ synchronized (mLock) {
+ mVibrationSettings.dumpProto(proto);
+ if (mCurrentVibration != null) {
+ mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+ VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
+ }
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+ VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+ }
+
+ boolean isVibrating = false;
+ boolean isUnderExternalControl = false;
+ for (int i = 0; i < mVibrators.size(); i++) {
+ proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
+ isVibrating |= mVibrators.valueAt(i).isVibrating();
+ isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
+ }
+ proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
+ proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+ isUnderExternalControl);
+ }
+ mVibratorManagerRecords.dumpProto(proto);
+ proto.flush();
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback cb, ResultReceiver resultReceiver) {
@@ -515,8 +574,15 @@
return;
}
- if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
- mCurrentVibration.getVibration().attrs.getUsage())) {
+ Vibration vib = mCurrentVibration.getVibration();
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+ vib.uid, vib.opPkg, vib.attrs);
+
+ if (inputDevicesChanged || (ignoreStatus != null)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration because settings changed: "
+ + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
+ }
mCurrentVibration.cancel();
}
}
@@ -602,15 +668,56 @@
@GuardedBy("mLock")
private void endVibrationLocked(Vibration vib, Vibration.Status status) {
vib.end(status);
+ logVibrationStatus(vib.uid, vib.attrs, status);
mVibratorManagerRecords.record(vib);
}
@GuardedBy("mLock")
private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
vib.end(status);
+ logVibrationStatus(vib.externalVibration.getUid(),
+ vib.externalVibration.getVibrationAttributes(), status);
mVibratorManagerRecords.record(vib);
}
+ private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
+ switch (status) {
+ case IGNORED_BACKGROUND:
+ Slog.e(TAG, "Ignoring incoming vibration as process with"
+ + " uid= " + uid + " is background," + " attrs= " + attrs);
+ break;
+ case IGNORED_ERROR_APP_OPS:
+ Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
+ break;
+ case IGNORED_FOR_ALARM:
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+ }
+ break;
+ case IGNORED_FOR_EXTERNAL:
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ }
+ break;
+ case IGNORED_FOR_ONGOING:
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of repeating vibration");
+ }
+ break;
+ case IGNORED_FOR_RINGER_MODE:
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration because of ringer mode, attrs="
+ + attrs);
+ }
+ break;
+ default:
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
+ + " ended with status " + status);
+ }
+ }
+ }
+
@GuardedBy("mLock")
private void reportFinishedVibrationLocked(Vibration.Status status) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -651,32 +758,36 @@
}
/**
- * Check if given vibration should be ignored in favour of one of the vibrations currently
- * running on the same vibrators.
+ * Check if given vibration should be ignored by this service because of the ongoing vibration.
*
- * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored, null
+ * otherwise.
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.Status shouldIgnoreVibrationForCurrentLocked(Vibration vibration) {
- if (vibration.isRepeating()) {
- // Repeating vibrations always take precedence.
+ private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+ if (mCurrentExternalVibration != null) {
+ // If something has external control of the vibrator, assume that it's more important.
+ return Vibration.Status.IGNORED_FOR_EXTERNAL;
+ }
+
+ if (mCurrentVibration == null || vib.isRepeating()) {
+ // Incoming repeating vibrations always take precedence over ongoing vibrations.
return null;
}
- if (mCurrentVibration != null && !mCurrentVibration.getVibration().hasEnded()) {
- if (mCurrentVibration.getVibration().attrs.getUsage()
- == VibrationAttributes.USAGE_ALARM) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
- }
- return Vibration.Status.IGNORED_FOR_ALARM;
- }
- if (mCurrentVibration.getVibration().isRepeating()) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of repeating vibration");
- }
- return Vibration.Status.IGNORED_FOR_ONGOING;
- }
+
+ Vibration currentVibration = mCurrentVibration.getVibration();
+ if (currentVibration.hasEnded()) {
+ // Current vibration is finishing up, it should not block incoming vibrations.
+ return null;
+ }
+
+ if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_ALARM) {
+ return Vibration.Status.IGNORED_FOR_ALARM;
+ }
+
+ if (currentVibration.isRepeating()) {
+ return Vibration.Status.IGNORED_FOR_ONGOING;
}
return null;
}
@@ -684,57 +795,16 @@
/**
* Check if given vibration should be ignored by this service.
*
- * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
- * @see #shouldIgnoreVibrationLocked(int, String, VibrationAttributes)
- */
- @GuardedBy("mLock")
- @Nullable
- private Vibration.Status shouldIgnoreVibrationLocked(Vibration vib) {
- // If something has external control of the vibrator, assume that it's more important.
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
- }
- return Vibration.Status.IGNORED_FOR_EXTERNAL;
- }
-
- if (!mVibrationSettings.shouldVibrateForUid(vib.uid, vib.attrs.getUsage())) {
- Slog.e(TAG, "Ignoring incoming vibration as process with"
- + " uid= " + vib.uid + " is background,"
- + " attrs= " + vib.attrs);
- return Vibration.Status.IGNORED_BACKGROUND;
- }
-
- return shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
- }
-
- /**
- * Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be
- * ignored by this service.
- *
- * @param uid The user id of this vibration
- * @param opPkg The package name of this vibration
- * @param attrs The attributes of this vibration
- * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored, null
+ * otherwise.
*/
@GuardedBy("mLock")
@Nullable
private Vibration.Status shouldIgnoreVibrationLocked(int uid, String opPkg,
VibrationAttributes attrs) {
- if (!mVibrationSettings.shouldVibrateForPowerMode(attrs.getUsage())) {
- return Vibration.Status.IGNORED_FOR_POWER;
- }
-
- int intensity = mVibrationSettings.getCurrentIntensity(attrs.getUsage());
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
- return Vibration.Status.IGNORED_FOR_SETTINGS;
- }
-
- if (!mVibrationSettings.shouldVibrateForRingerMode(attrs.getUsage())) {
- if (DEBUG) {
- Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
- }
- return Vibration.Status.IGNORED_RINGTONE;
+ Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(uid, attrs);
+ if (statusFromSettings != null) {
+ return statusFromSettings;
}
int mode = checkAppOpModeLocked(uid, opPkg, attrs);
@@ -742,7 +812,6 @@
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
- Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
return Vibration.Status.IGNORED_ERROR_APP_OPS;
} else {
return Vibration.Status.IGNORED_APP_OPS;
@@ -1236,21 +1305,18 @@
}
/** Keep records of vibrations played and provide debug information for this service. */
- private final class VibratorManagerRecords {
- @GuardedBy("mLock")
+ private static final class VibratorManagerRecords {
private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
new SparseArray<>();
- @GuardedBy("mLock")
private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
new LinkedList<>();
private final int mPreviousVibrationsLimit;
- private VibratorManagerRecords(int limit) {
+ VibratorManagerRecords(int limit) {
mPreviousVibrationsLimit = limit;
}
- @GuardedBy("mLock")
- void record(Vibration vib) {
+ synchronized void record(Vibration vib) {
int usage = vib.attrs.getUsage();
if (!mPreviousVibrations.contains(usage)) {
mPreviousVibrations.put(usage, new LinkedList<>());
@@ -1258,122 +1324,67 @@
record(mPreviousVibrations.get(usage), vib.getDebugInfo());
}
- @GuardedBy("mLock")
- void record(ExternalVibrationHolder vib) {
+ synchronized void record(ExternalVibrationHolder vib) {
record(mPreviousExternalVibrations, vib.getDebugInfo());
}
- @GuardedBy("mLock")
- void record(LinkedList<Vibration.DebugInfo> records, Vibration.DebugInfo info) {
+ synchronized void record(LinkedList<Vibration.DebugInfo> records,
+ Vibration.DebugInfo info) {
if (records.size() > mPreviousVibrationsLimit) {
records.removeFirst();
}
records.addLast(info);
}
- void dumpText(PrintWriter pw) {
- pw.println("Vibrator Manager Service:");
- synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Dumping vibrator manager service to text...");
- }
- pw.println(" mVibrationSettings:");
- pw.println(" " + mVibrationSettings);
+ synchronized void dumpText(PrintWriter pw) {
+ for (int i = 0; i < mPreviousVibrations.size(); i++) {
pw.println();
- pw.println(" mVibratorControllers:");
- for (int i = 0; i < mVibrators.size(); i++) {
- pw.println(" " + mVibrators.valueAt(i));
- }
- pw.println();
- pw.println(" mCurrentVibration:");
- pw.println(" " + (mCurrentVibration == null
- ? null : mCurrentVibration.getVibration().getDebugInfo()));
- pw.println();
- pw.println(" mNextVibration:");
- pw.println(" " + (mNextVibration == null
- ? null : mNextVibration.getVibration().getDebugInfo()));
- pw.println();
- pw.println(" mCurrentExternalVibration:");
- pw.println(" " + (mCurrentExternalVibration == null
- ? null : mCurrentExternalVibration.getDebugInfo()));
- pw.println();
- for (int i = 0; i < mPreviousVibrations.size(); i++) {
- pw.println();
- pw.print(" Previous vibrations for usage ");
- pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
- pw.println(":");
- for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
- pw.println(" " + info);
- }
- }
-
- pw.println();
- pw.println(" Previous external vibrations:");
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ pw.print(" Previous vibrations for usage ");
+ pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
+ pw.println(":");
+ for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
pw.println(" " + info);
}
}
+
+ pw.println();
+ pw.println(" Previous external vibrations:");
+ for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ pw.println(" " + info);
+ }
}
- synchronized void dumpProto(FileDescriptor fd) {
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
- synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Dumping vibrator manager service to proto...");
+ synchronized void dumpProto(ProtoOutputStream proto) {
+ for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ long fieldId;
+ switch (mPreviousVibrations.keyAt(i)) {
+ case VibrationAttributes.USAGE_RINGTONE:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+ break;
+ case VibrationAttributes.USAGE_NOTIFICATION:
+ fieldId = VibratorManagerServiceDumpProto
+ .PREVIOUS_NOTIFICATION_VIBRATIONS;
+ break;
+ case VibrationAttributes.USAGE_ALARM:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+ break;
+ default:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
}
- mVibrationSettings.dumpProto(proto);
- if (mCurrentVibration != null) {
- mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
- VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
- }
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
- VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
- }
-
- boolean isVibrating = false;
- boolean isUnderExternalControl = false;
- for (int i = 0; i < mVibrators.size(); i++) {
- proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
- isVibrating |= mVibrators.valueAt(i).isVibrating();
- isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
- }
- proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
- proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
- isUnderExternalControl);
-
- for (int i = 0; i < mPreviousVibrations.size(); i++) {
- long fieldId;
- switch (mPreviousVibrations.keyAt(i)) {
- case VibrationAttributes.USAGE_RINGTONE:
- fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
- break;
- case VibrationAttributes.USAGE_NOTIFICATION:
- fieldId = VibratorManagerServiceDumpProto
- .PREVIOUS_NOTIFICATION_VIBRATIONS;
- break;
- case VibrationAttributes.USAGE_ALARM:
- fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
- break;
- default:
- fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
- }
- for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
- info.dumpProto(proto, fieldId);
- }
- }
-
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto,
- VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+ info.dumpProto(proto, fieldId);
}
}
- proto.flush();
+
+ for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ info.dumpProto(proto,
+ VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ }
}
}
/** Clears mNextVibration if set, ending it cleanly */
+ @GuardedBy("mLock")
private void clearNextVibrationLocked(Vibration.Status endStatus) {
if (mNextVibration != null) {
endVibrationLocked(mNextVibration.getVibration(), endStatus);
@@ -1482,6 +1493,7 @@
}
}
+ @GuardedBy("mLock")
private void stopExternalVibrateLocked(Vibration.Status status) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
try {
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 00e1f55..d736ede 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.TaskInfo;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
@@ -40,6 +41,12 @@
public abstract @Nullable Intent intercept(ActivityInterceptorInfo info);
/**
+ * Called when an activity is successfully launched.
+ */
+ public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+ }
+
+ /**
* The unique id of each interceptor which determines the order it will execute in.
*/
@IntDef(suffix = { "_ORDERED_ID" }, value = {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 100c44b..2b28478 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -830,6 +830,8 @@
// SystemUi sets the pinned mode on activity after transition is done.
boolean mWaitForEnteringPinnedMode;
+ private final ActivityRecordInputSink mActivityRecordInputSink;
+
private final Runnable mPauseTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -1046,6 +1048,8 @@
pw.print(" forceNewConfig="); pw.println(forceNewConfig);
pw.print(prefix); pw.print("mActivityType=");
pw.println(activityTypeToString(getActivityType()));
+ pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput=");
+ pw.println(mImeInsetsFrozenUntilStartInput);
if (requestedVrComponent != null) {
pw.print(prefix);
pw.print("requestedVrComponent=");
@@ -1785,6 +1789,8 @@
createTime = _createTime;
}
mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
+
+ mActivityRecordInputSink = new ActivityRecordInputSink(this);
}
/**
@@ -6771,6 +6777,10 @@
} else if (!show && mLastSurfaceShowing) {
getSyncTransaction().hide(mSurfaceControl);
}
+ if (show) {
+ mActivityRecordInputSink.applyChangesToSurfaceIfChanged(
+ getSyncTransaction(), mSurfaceControl);
+ }
}
if (mThumbnail != null) {
mThumbnail.setShowing(getPendingTransaction(), show);
@@ -7996,7 +8006,8 @@
if (mVisibleRequested) {
// It may toggle the UI for user to restart the size compatibility mode activity.
display.handleActivitySizeCompatModeIfNeeded(this);
- } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard) {
+ } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard
+ && (app == null || !app.hasVisibleActivities())) {
// visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during
// displays change. Displays are turned off during the change so mVisibleRequested
// can be false.
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
new file mode 100644
index 0000000..b183281
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -0,0 +1,171 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
+import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
+
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.os.IBinder;
+import android.os.InputConstants;
+import android.os.Looper;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputWindowHandle;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+/**
+ * Creates a InputWindowHandle that catches all touches that would otherwise pass through an
+ * Activity.
+ */
+class ActivityRecordInputSink {
+
+ /**
+ * Feature flag for making Activities consume all touches within their task bounds.
+ */
+ @ChangeId
+ @Disabled
+ static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
+
+ private static final String TAG = "ActivityRecordInputSink";
+ private static final int NUMBER_OF_TOUCHES_TO_DISABLE = 3;
+ private static final long TOAST_COOL_DOWN_MILLIS = 3000L;
+
+ private final ActivityRecord mActivityRecord;
+ private final boolean mIsCompatEnabled;
+
+ // Hold on to InputEventReceiver to prevent it from getting GCd.
+ private InputEventReceiver mInputEventReceiver;
+ private InputWindowHandleWrapper mInputWindowHandleWrapper;
+ private final String mName = Integer.toHexString(System.identityHashCode(this))
+ + " ActivityRecordInputSink";
+ private int mRapidTouchCount = 0;
+ private IBinder mToken;
+ private boolean mDisabled = false;
+
+ ActivityRecordInputSink(ActivityRecord activityRecord) {
+ mActivityRecord = activityRecord;
+ mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
+ mActivityRecord.getUid());
+ }
+
+ public void applyChangesToSurfaceIfChanged(
+ SurfaceControl.Transaction transaction, SurfaceControl surfaceControl) {
+ InputWindowHandleWrapper inputWindowHandleWrapper = getInputWindowHandleWrapper();
+ if (inputWindowHandleWrapper.isChanged()) {
+ inputWindowHandleWrapper.applyChangesToSurface(transaction, surfaceControl);
+ }
+ }
+
+ private InputWindowHandleWrapper getInputWindowHandleWrapper() {
+ if (mInputWindowHandleWrapper == null) {
+ mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle());
+ InputChannel inputChannel =
+ mActivityRecord.mWmService.mInputManager.createInputChannel(mName);
+ mToken = inputChannel.getToken();
+ mInputEventReceiver = createInputEventReceiver(inputChannel);
+ }
+ if (mDisabled || !mIsCompatEnabled || mActivityRecord.isAnimating(TRANSITION | PARENTS,
+ ANIMATION_TYPE_APP_TRANSITION)) {
+ // TODO(b/208662670): Investigate if we can have feature active during animations.
+ mInputWindowHandleWrapper.setToken(null);
+ } else if (mActivityRecord.mStartingData != null) {
+ // TODO(b/208659130): Remove this special case
+ // Don't block touches during splash screen. This is done to not show toasts for
+ // touches passing through splash screens. b/171772640
+ mInputWindowHandleWrapper.setToken(null);
+ } else {
+ mInputWindowHandleWrapper.setToken(mToken);
+ }
+ return mInputWindowHandleWrapper;
+ }
+
+ private InputWindowHandle createInputWindowHandle() {
+ InputWindowHandle inputWindowHandle = new InputWindowHandle(
+ mActivityRecord.getInputApplicationHandle(false),
+ mActivityRecord.getDisplayId());
+ inputWindowHandle.replaceTouchableRegionWithCrop(
+ mActivityRecord.getParentSurfaceControl());
+ inputWindowHandle.name = mName;
+ inputWindowHandle.ownerUid = mActivityRecord.getUid();
+ inputWindowHandle.ownerPid = mActivityRecord.getPid();
+ inputWindowHandle.layoutParamsFlags =
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+ inputWindowHandle.dispatchingTimeoutMillis =
+ InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+ return inputWindowHandle;
+ }
+
+ private InputEventReceiver createInputEventReceiver(InputChannel inputChannel) {
+ return new SinkInputEventReceiver(inputChannel,
+ mActivityRecord.mAtmService.mUiHandler.getLooper());
+ }
+
+ private void showAsToastAndLog(String message) {
+ Toast.makeText(mActivityRecord.mAtmService.mUiContext, message,
+ Toast.LENGTH_LONG).show();
+ Slog.wtf(TAG, message + " " + mActivityRecord.mActivityComponent);
+ }
+
+ private class SinkInputEventReceiver extends InputEventReceiver {
+ private long mLastToast = 0;
+
+ SinkInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ public void onInputEvent(InputEvent event) {
+ if (!(event instanceof MotionEvent)) {
+ Slog.wtf(TAG,
+ "Received InputEvent that was not a MotionEvent");
+ finishInputEvent(event, true);
+ return;
+ }
+ MotionEvent motionEvent = (MotionEvent) event;
+ if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) {
+ finishInputEvent(event, true);
+ return;
+ }
+
+ if (event.getEventTime() - mLastToast > TOAST_COOL_DOWN_MILLIS) {
+ String message = "go/activity-touch-opaque - "
+ + mActivityRecord.mActivityComponent.getPackageName()
+ + " blocked the touch!";
+ showAsToastAndLog(message);
+ mLastToast = event.getEventTime();
+ mRapidTouchCount = 1;
+ } else if (++mRapidTouchCount >= NUMBER_OF_TOUCHES_TO_DISABLE && !mDisabled) {
+ // Disable touch blocking until Activity Record is recreated.
+ String message = "Disabled go/activity-touch-opaque - "
+ + mActivityRecord.mActivityComponent.getPackageName();
+ showAsToastAndLog(message);
+ mDisabled = true;
+ }
+ finishInputEvent(event, true);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 223f0be..352a070 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -37,6 +37,7 @@
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
+import android.app.TaskInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.IIntentSender;
@@ -402,4 +403,16 @@
mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
return true;
}
+
+ /**
+ * Called when an activity is successfully launched.
+ */
+ void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+ final SparseArray<ActivityInterceptorCallback> callbacks =
+ mService.getActivityInterceptorCallbacks();
+ for (int i = 0; i < callbacks.size(); i++) {
+ final ActivityInterceptorCallback callback = callbacks.valueAt(i);
+ callback.onActivityLaunched(taskInfo, activityInfo);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bb7434d..471b4ce 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1559,6 +1559,10 @@
mService.getTaskChangeNotificationController().notifyActivityRestartAttempt(
targetTask.getTaskInfo(), homeTaskVisible, clearedTask, visible);
}
+
+ if (ActivityManager.isStartResultSuccessful(result)) {
+ mInterceptor.onActivityLaunched(targetTask.getTaskInfo(), r.info);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index e38e9c1..7fa9861 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -126,6 +126,35 @@
}
/**
+ * Sleep tokens cause the activity manager to put the top activity to sleep.
+ * They are used by components such as dreams that may hide and block interaction
+ * with underlying activities.
+ * The Acquirer provides an interface that encapsulates the underlying work, so the user does
+ * not need to handle the token by him/herself.
+ */
+ public interface SleepTokenAcquirer {
+
+ /**
+ * Acquires a sleep token.
+ * @param displayId The display to apply to.
+ */
+ void acquire(int displayId);
+
+ /**
+ * Releases the sleep token.
+ * @param displayId The display to apply to.
+ */
+ void release(int displayId);
+ }
+
+ /**
+ * Creates a sleep token acquirer for the specified display with the specified tag.
+ *
+ * @param tag A string identifying the purpose (eg. "Dream").
+ */
+ public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag);
+
+ /**
* Returns home activity for the specified user.
*
* @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e4ed04de..0a85ba1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4544,25 +4544,17 @@
reason);
}
- /**
- * Sleep tokens cause the activity manager to put the top activity to sleep.
- * They are used by components such as dreams that may hide and block interaction
- * with underlying activities.
- */
- final class SleepTokenAcquirer {
+ final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
private final String mTag;
private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens =
new SparseArray<>();
- SleepTokenAcquirer(@NonNull String tag) {
+ SleepTokenAcquirerImpl(@NonNull String tag) {
mTag = tag;
}
- /**
- * Acquires a sleep token.
- * @param displayId The display to apply to.
- */
- void acquire(int displayId) {
+ @Override
+ public void acquire(int displayId) {
synchronized (mGlobalLock) {
if (!mSleepTokens.contains(displayId)) {
mSleepTokens.append(displayId,
@@ -4572,11 +4564,8 @@
}
}
- /**
- * Releases the sleep token.
- * @param displayId The display to apply to.
- */
- void release(int displayId) {
+ @Override
+ public void release(int displayId) {
synchronized (mGlobalLock) {
final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId);
if (token != null) {
@@ -5266,6 +5255,12 @@
final class LocalService extends ActivityTaskManagerInternal {
@Override
+ public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) {
+ Objects.requireNonNull(tag);
+ return new SleepTokenAcquirerImpl(tag);
+ }
+
+ @Override
public ComponentName getHomeActivityForUser(int userId) {
synchronized (mGlobalLock) {
final ActivityRecord homeActivity =
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3b4a31b..0956580 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -664,7 +664,7 @@
/** All tokens used to put activities on this root task to sleep (including mOffToken) */
final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
/** The token acquirer to put root tasks on the display to sleep */
- private final ActivityTaskManagerService.SleepTokenAcquirer mOffTokenAcquirer;
+ private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer;
private boolean mSleeping;
@@ -1585,8 +1585,9 @@
@Override
boolean isSyncFinished() {
- if (mDisplayRotation.isWaitingForRemoteRotation()) return false;
- return super.isSyncFinished();
+ // Do not consider children because if they are requested to be synced, they should be
+ // added to sync group explicitly.
+ return !mDisplayRotation.isWaitingForRemoteRotation();
}
/**
@@ -1876,16 +1877,16 @@
}
}
- /** Shows the given window which may be hidden for screen frozen. */
- void finishFadeRotationAnimation(WindowState w) {
+ /** Shows the given window which may be hidden for screen rotation. */
+ void finishFadeRotationAnimation(WindowToken windowToken) {
final FadeRotationAnimationController controller = mFadeRotationAnimationController;
- if (controller != null && controller.show(w.mToken)) {
+ if (controller != null && controller.show(windowToken)) {
mFadeRotationAnimationController = null;
}
}
- /** Returns {@code true} if the display should wait for the given window to stop freezing. */
- boolean waitForUnfreeze(WindowState w) {
+ /** Returns {@code true} if the screen rotation animation needs to wait for the window. */
+ boolean shouldSyncRotationChange(WindowState w) {
if (w.mForceSeamlesslyRotate) {
// The window should look no different before and after rotation.
return false;
@@ -3230,6 +3231,7 @@
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
controller.mTransitionMetricsReporter.associate(t,
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+ startFadeRotationAnimation(false /* shouldDebounce */);
}
t.setKnownConfigChanges(this, changes);
}
@@ -4136,11 +4138,11 @@
* which controls the visibility and animation of the input method window.
*/
void updateImeInputAndControlTarget(WindowState target) {
+ if (target != null && target.mActivityRecord != null) {
+ target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
+ }
if (mImeInputTarget != target) {
ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
- if (target != null && target.mActivityRecord != null) {
- target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
- }
setImeInputTarget(target);
mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController
.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
@@ -5479,14 +5481,6 @@
return mMetricsLogger;
}
- void acquireScreenOffToken(boolean acquire) {
- if (acquire) {
- mOffTokenAcquirer.acquire(mDisplayId);
- } else {
- mOffTokenAcquirer.release(mDisplayId);
- }
- }
-
void onDisplayChanged() {
mDisplay.getRealSize(mTmpDisplaySize);
setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
@@ -5498,9 +5492,9 @@
final int displayState = mDisplayInfo.state;
if (displayId != DEFAULT_DISPLAY) {
if (displayState == Display.STATE_OFF) {
- acquireScreenOffToken(true /* acquire */);
+ mOffTokenAcquirer.acquire(mDisplayId);
} else if (displayState == Display.STATE_ON) {
- acquireScreenOffToken(false /* acquire */);
+ mOffTokenAcquirer.release(mDisplayId);
}
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Display %d state is now (%d), so update layer mirroring?",
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7a2a311..71ab5b6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -752,10 +752,6 @@
public void setAwake(boolean awake) {
mAwake = awake;
- // The screen off token for non-default display is controlled by DisplayContent.
- if (mDisplayContent.isDefaultDisplay) {
- mDisplayContent.acquireScreenOffToken(!awake);
- }
}
public boolean isAwake() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 427bbeb..4684cb1 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -508,10 +508,10 @@
mDisplayContent.setLayoutNeeded();
if (useShellTransitions) {
- final boolean wasInTransition = mDisplayContent.inTransition();
+ final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
mDisplayContent.requestChangeTransitionIfNeeded(
ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
- if (wasInTransition) {
+ if (wasCollecting) {
// Use remote-rotation infra since the transition has already been requested
// TODO(shell-transitions): Remove this once lifecycle management can cover all
// rotation cases.
@@ -595,12 +595,8 @@
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
// handling is synchronized.
- mDisplayContent.forAllTasks(task -> {
- if (task.isVisible()) {
- mDisplayContent.mTransitionController.collect(task);
- }
- });
- mDisplayContent.getInsetsStateController().addProvidersToTransition();
+ mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent,
+ null /* use collecting transition */);
}
mService.mAtmService.deferWindowLayout();
try {
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 2f3ad40..817b27a 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -36,10 +36,12 @@
* An animation controller to fade-in/out for a window token.
*/
public class FadeAnimationController {
+ protected final DisplayContent mDisplayContent;
protected final Context mContext;
protected final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();
public FadeAnimationController(DisplayContent displayContent) {
+ mDisplayContent = displayContent;
mContext = displayContent.mWmService.mContext;
}
@@ -69,7 +71,9 @@
return;
}
- final FadeAnimationAdapter animationAdapter = createAdapter(show, windowToken);
+ final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
+ final FadeAnimationAdapter animationAdapter = animation != null
+ ? createAdapter(createAnimationSpec(animation), show, windowToken) : null;
if (animationAdapter == null) {
return;
}
@@ -86,17 +90,10 @@
show /* hidden */, animationType, finishedCallback);
}
- protected FadeAnimationAdapter createAdapter(boolean show, WindowToken windowToken) {
- final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
- if (animation == null) {
- return null;
- }
-
- final LocalAnimationAdapter.AnimationSpec windowAnimationSpec =
- createAnimationSpec(animation);
-
- return new FadeAnimationAdapter(
- windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken);
+ protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
+ boolean show, WindowToken windowToken) {
+ return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(),
+ show, windowToken);
}
protected LocalAnimationAdapter.AnimationSpec createAnimationSpec(
@@ -140,7 +137,7 @@
protected class FadeAnimationAdapter extends LocalAnimationAdapter {
protected final boolean mShow;
- private final WindowToken mToken;
+ protected final WindowToken mToken;
FadeAnimationAdapter(AnimationSpec windowAnimationSpec,
SurfaceAnimationRunner surfaceAnimationRunner, boolean show,
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
index 52a7ac7..cf36c85 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -18,6 +18,10 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+import android.os.HandlerExecutor;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -33,10 +37,11 @@
*/
public class FadeRotationAnimationController extends FadeAnimationController {
- private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
+ /** The map of window token to its animation leash. */
+ private final ArrayMap<WindowToken, SurfaceControl> mTargetWindowTokens = new ArrayMap<>();
private final WindowManagerService mService;
/** If non-null, it usually indicates that there will be a screen rotation animation. */
- private final Runnable mFrozenTimeoutRunnable;
+ private final Runnable mTimeoutRunnable;
private final WindowToken mNavBarToken;
/** A runnable which gets called when the {@link #show()} is called. */
@@ -45,16 +50,30 @@
/** Whether to use constant zero alpha animation. */
private boolean mHideImmediately;
+ /** Whether this controller is triggered from shell transition. */
+ private final boolean mIsChangeTransition;
+
+ /** Whether the start transaction of the transition is committed (by shell). */
+ private boolean mIsStartTransactionCommitted;
+
+ /** The list to store the drawn tokens before the rotation animation starts. */
+ private ArrayList<WindowToken> mPendingShowTokens;
+
public FadeRotationAnimationController(DisplayContent displayContent) {
super(displayContent);
mService = displayContent.mWmService;
- mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
+ mIsChangeTransition = displayContent.inTransition()
+ && displayContent.mTransitionController.getCollectingTransitionType()
+ == WindowManager.TRANSIT_CHANGE;
+ mIsStartTransactionCommitted = !mIsChangeTransition;
+ mTimeoutRunnable = displayContent.getRotationAnimation() != null
+ || mIsChangeTransition ? () -> {
synchronized (mService.mGlobalLock) {
displayContent.finishFadeRotationAnimationIfPossible();
mService.mWindowPlacerLocked.performSurfacePlacement();
}
} : null;
- if (mFrozenTimeoutRunnable != null) {
+ if (mTimeoutRunnable != null) {
// Hide the windows immediately because screen should have been covered by screenshot.
mHideImmediately = true;
}
@@ -68,7 +87,7 @@
// Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
// bar is currently controlled by recents animation.
if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
- mTargetWindowTokens.add(mNavBarToken);
+ mTargetWindowTokens.put(mNavBarToken, null);
}
} else {
mNavBarToken = null;
@@ -79,7 +98,7 @@
if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
&& !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar
&& w != notificationShade) {
- mTargetWindowTokens.add(w.mToken);
+ mTargetWindowTokens.put(w.mToken, null);
}
}, true /* traverseTopToBottom */);
}
@@ -87,12 +106,13 @@
/** Applies show animation on the previously hidden window tokens. */
void show() {
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
- final WindowToken windowToken = mTargetWindowTokens.get(i);
+ final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
}
mTargetWindowTokens.clear();
- if (mFrozenTimeoutRunnable != null) {
- mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ mPendingShowTokens = null;
+ if (mTimeoutRunnable != null) {
+ mService.mH.removeCallbacks(mTimeoutRunnable);
}
if (mOnShowRunnable != null) {
mOnShowRunnable.run();
@@ -105,10 +125,22 @@
* controller is created for normal rotation.
*/
boolean show(WindowToken token) {
- if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
+ if (!mIsStartTransactionCommitted) {
+ // The fade-in animation should only start after the screenshot layer is shown by shell.
+ // Otherwise the window will be blinking before the rotation animation starts. So store
+ // to a pending list and animate them until the transaction is committed.
+ if (mTargetWindowTokens.containsKey(token)) {
+ if (mPendingShowTokens == null) {
+ mPendingShowTokens = new ArrayList<>();
+ }
+ mPendingShowTokens.add(token);
+ }
+ return false;
+ }
+ if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) {
fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
if (mTargetWindowTokens.isEmpty()) {
- mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ mService.mH.removeCallbacks(mTimeoutRunnable);
return true;
}
}
@@ -118,11 +150,11 @@
/** Applies hide animation on the window tokens which may be seamlessly rotated later. */
void hide() {
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
- final WindowToken windowToken = mTargetWindowTokens.get(i);
+ final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
}
- if (mFrozenTimeoutRunnable != null) {
- mService.mH.postDelayed(mFrozenTimeoutRunnable,
+ if (mTimeoutRunnable != null) {
+ mService.mH.postDelayed(mTimeoutRunnable,
WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
}
}
@@ -131,7 +163,6 @@
void hideImmediately(WindowToken windowToken) {
final boolean original = mHideImmediately;
mHideImmediately = true;
- mTargetWindowTokens.add(windowToken);
fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
mHideImmediately = original;
}
@@ -143,16 +174,43 @@
/** Returns {@code true} if the controller will run fade animations on the window. */
boolean isTargetToken(WindowToken token) {
- return mTargetWindowTokens.contains(token);
+ return mTargetWindowTokens.containsKey(token);
}
void setOnShowRunnable(Runnable onShowRunnable) {
mOnShowRunnable = onShowRunnable;
}
+ /**
+ * Puts initial operation of leash to the transaction which will be executed when the
+ * transition starts. And associate transaction callback to consume pending animations.
+ */
+ void setupStartTransaction(SurfaceControl.Transaction t) {
+ // Hide the windows immediately because a screenshot layer should cover the screen.
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
+ if (leash != null) {
+ t.setAlpha(leash, 0f);
+ }
+ }
+ // If there are windows have redrawn in new rotation but the start transaction has not
+ // been applied yet, the fade-in animation will be deferred. So once the transaction is
+ // committed, the fade-in animation can run with screen rotation animation.
+ t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> {
+ synchronized (mService.mGlobalLock) {
+ mIsStartTransactionCommitted = true;
+ if (mPendingShowTokens == null) return;
+ for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
+ mDisplayContent.finishFadeRotationAnimation(mPendingShowTokens.get(i));
+ }
+ mPendingShowTokens = null;
+ }
+ });
+ }
+
@Override
public Animation getFadeInAnimation() {
- if (mFrozenTimeoutRunnable != null) {
+ if (mTimeoutRunnable != null) {
// Use a shorter animation so it is easier to align with screen rotation animation.
return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
}
@@ -162,8 +220,28 @@
@Override
public Animation getFadeOutAnimation() {
if (mHideImmediately) {
- return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
+ // For change transition, the hide transaction needs to be applied with sync transaction
+ // (setupStartTransaction). So keep alpha 1 just to get the animation leash.
+ final float alpha = mIsChangeTransition ? 1 : 0;
+ return new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */);
}
return super.getFadeOutAnimation();
}
+
+ @Override
+ protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
+ boolean show, WindowToken windowToken) {
+ return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(),
+ show, windowToken) {
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+ int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ // The fade cycle is done when showing, so only need to store the leash when hiding.
+ if (!show) {
+ mTargetWindowTokens.put(mToken, animationLeash);
+ }
+ super.startAnimation(animationLeash, t, type, finishCallback);
+ }
+ };
+ }
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 405a9e5..e33c440 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -249,16 +249,6 @@
return result;
}
- public void addProvidersToTransition() {
- for (int i = mProviders.size() - 1; i >= 0; --i) {
- final InsetsSourceProvider p = mProviders.valueAt(i);
- if (p == null) continue;
- final WindowContainer wc = p.mWin;
- if (wc == null) continue;
- mDisplayContent.mTransitionController.collect(wc);
- }
- }
-
/**
* @return The provider of a specific type.
*/
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cabe414..baf7f87 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -75,14 +75,14 @@
private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
private final ActivityTaskManagerService mService;
private RootWindowContainer mRootWindowContainer;
- private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+ private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
KeyguardController(ActivityTaskManagerService service,
ActivityTaskSupervisor taskSupervisor) {
mService = service;
mTaskSupervisor = taskSupervisor;
- mSleepTokenAcquirer = mService.new SleepTokenAcquirer(KEYGUARD_SLEEP_TOKEN_TAG);
+ mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
}
void setWindowManager(WindowManagerService windowManager) {
@@ -518,10 +518,10 @@
private boolean mRequestDismissKeyguard;
private final ActivityTaskManagerService mService;
- private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+ private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
- ActivityTaskManagerService.SleepTokenAcquirer acquirer) {
+ ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
mService = service;
mDisplayId = displayId;
mSleepTokenAcquirer = acquirer;
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index 7abf3b8..af8293a 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -37,7 +37,6 @@
private static final Interpolator FADE_OUT_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 1f, 1f);
- private DisplayContent mDisplayContent;
private final WindowState mNavigationBar;
private Animation mFadeInAnimation;
private Animation mFadeOutAnimation;
@@ -47,7 +46,6 @@
public NavBarFadeAnimationController(DisplayContent displayContent) {
super(displayContent);
- mDisplayContent = displayContent;
mNavigationBar = displayContent.getDisplayPolicy().getNavigationBar();
mFadeInAnimation = new AlphaAnimation(0f, 1f);
mFadeInAnimation.setDuration(FADE_IN_DURATION);
@@ -69,16 +67,10 @@
}
@Override
- protected FadeAnimationAdapter createAdapter(boolean show, WindowToken windowToken) {
- final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
- if (animation == null) {
- return null;
- }
-
- final LocalAnimationAdapter.AnimationSpec windowAnimationSpec =
- createAnimationSpec(animation);
+ protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
+ boolean show, WindowToken windowToken) {
return new NavFadeAnimationAdapter(
- windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken,
+ animationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken,
show ? mFadeInParent : mFadeOutParent);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 117b22a..c7b13eb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -225,7 +225,7 @@
private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
/** The token acquirer to put root tasks on the displays to sleep */
- final ActivityTaskManagerService.SleepTokenAcquirer mDisplayOffTokenAcquirer;
+ final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
/**
* The modes which affect which tasks are returned when calling
@@ -470,7 +470,7 @@
mService = service.mAtmService;
mTaskSupervisor = mService.mTaskSupervisor;
mTaskSupervisor.mRootWindowContainer = this;
- mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirer(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+ mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b328e4d..f0b55cb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4600,23 +4600,14 @@
moveToFront(reason, null);
}
- /**
- * @param reason The reason for moving the root task to the front.
- * @param task If non-null, the task will be moved to the top of the root task.
- */
void moveToFront(String reason, Task task) {
- if (!isAttached()) {
- return;
- }
-
- final TaskDisplayArea taskDisplayArea = getDisplayArea();
-
if (inSplitScreenSecondaryWindowingMode()) {
// If the root task is in split-screen secondary mode, we need to make sure we move the
// primary split-screen root task forward in the case it is currently behind a
// fullscreen root task so both halves of the split-screen appear on-top and the
// fullscreen root task isn't cutting between them.
// TODO(b/70677280): This is a workaround until we can fix as part of b/70677280.
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
final Task topFullScreenRootTask =
taskDisplayArea.getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
if (topFullScreenRootTask != null) {
@@ -4624,10 +4615,30 @@
taskDisplayArea.getRootSplitScreenPrimaryTask();
if (primarySplitScreenRootTask != null
&& topFullScreenRootTask.compareTo(primarySplitScreenRootTask) > 0) {
- primarySplitScreenRootTask.moveToFront(reason + " splitScreenToTop");
+ primarySplitScreenRootTask.moveToFrontInner(reason + " splitScreenToTop",
+ null /* task */);
}
}
+ } else if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) {
+ final Task adjacentTask = getAdjacentTaskFragment().asTask();
+ if (adjacentTask != null) {
+ adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */);
+ }
}
+ moveToFrontInner(reason, task);
+ }
+
+ /**
+ * @param reason The reason for moving the root task to the front.
+ * @param task If non-null, the task will be moved to the top of the root task.
+ */
+ @VisibleForTesting
+ void moveToFrontInner(String reason, Task task) {
+ if (!isAttached()) {
+ return;
+ }
+
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (!isActivityTypeHome() && returnsToHomeRootTask()) {
// Make sure the root home task is behind this root task since that is where we
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e497b53..59a5cdf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -168,6 +168,14 @@
private TaskFragment mAdjacentTaskFragment;
/**
+ * Whether to move adjacent task fragment together when re-positioning.
+ *
+ * @see #mAdjacentTaskFragment
+ */
+ // TODO(b/207185041): Remove this once having a single-top root for split screen.
+ boolean mMoveAdjacentTogether;
+
+ /**
* Prevents duplicate calls to onTaskAppeared.
*/
boolean mTaskFragmentAppearedSent;
@@ -309,14 +317,15 @@
return service.mWindowOrganizerController.getTaskFragment(token);
}
- void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
+ void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment, boolean moveTogether) {
if (mAdjacentTaskFragment == taskFragment) {
return;
}
resetAdjacentTaskFragment();
if (taskFragment != null) {
mAdjacentTaskFragment = taskFragment;
- taskFragment.setAdjacentTaskFragment(this);
+ mMoveAdjacentTogether = moveTogether;
+ taskFragment.setAdjacentTaskFragment(this, moveTogether);
}
}
@@ -325,9 +334,11 @@
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
mAdjacentTaskFragment.mAdjacentTaskFragment = null;
mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
+ mAdjacentTaskFragment.mMoveAdjacentTogether = false;
}
mAdjacentTaskFragment = null;
mDelayLastActivityRemoval = false;
+ mMoveAdjacentTogether = false;
}
void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@@ -1942,7 +1953,15 @@
if (inOutConfig.smallestScreenWidthDp
== Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
- if (WindowConfiguration.isFloating(windowingMode)) {
+ // When entering to or exiting from Pip, the PipTaskOrganizer will set the
+ // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and
+ // temporarily set the bounds of the task to fullscreen size for transitioning.
+ // It will get the wrong value if the calculation is based on this temporary
+ // fullscreen bounds.
+ // We should just inherit the value from parent for this temporary state.
+ final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
+ && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
+ if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
// For floating tasks, calculate the smallest width from the bounds of the task
inOutConfig.smallestScreenWidthDp = (int) (
Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7349594..3974747 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -596,6 +596,12 @@
}
}
+ // This is non-null only if display has changes. It handles the visible windows that don't
+ // need to be participated in the transition.
+ final FadeRotationAnimationController controller = dc.getFadeRotationAnimationController();
+ if (controller != null) {
+ controller.setupStartTransaction(transaction);
+ }
mStartTransaction = transaction;
mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e054570..99dfe13 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -337,6 +337,29 @@
mCollectingTransition.collectExistenceChange(wc);
}
+ /**
+ * Collects the window containers which need to be synced with the changing display (e.g.
+ * rotating) to the given transition or the current collecting transition.
+ */
+ void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) {
+ if (incoming == null) incoming = mCollectingTransition;
+ if (incoming == null) return;
+ final Transition transition = incoming;
+ // Collect all visible tasks.
+ dc.forAllLeafTasks(task -> {
+ if (task.isVisible()) {
+ transition.collect(task);
+ }
+ }, true /* traverseTopToBottom */);
+ // Collect all visible non-app windows which need to be drawn before the animation starts.
+ dc.forAllWindows(w -> {
+ if (w.mActivityRecord == null && w.isVisible() && !inTransition(w.mToken)
+ && dc.shouldSyncRotationChange(w)) {
+ transition.collect(w.mToken);
+ }
+ }, true /* traverseTopToBottom */);
+ }
+
/** @see Transition#setOverrideAnimation */
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0649b25..525d84be 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -335,10 +335,7 @@
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once
// wallpaper handling is synchronized.
- dc.forAllTasks(task -> {
- if (task.isVisible()) transition.collect(task);
- });
- dc.getInsetsStateController().addProvidersToTransition();
+ dc.mTransitionController.collectForDisplayChange(dc, transition);
dc.sendNewConfiguration();
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -664,7 +661,7 @@
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
break;
}
- tf1.setAdjacentTaskFragment(tf2);
+ tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
final Bundle bundle = hop.getLaunchOptions();
@@ -977,7 +974,7 @@
throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+ " organizer root1=" + root1 + " root2=" + root2);
}
- root1.setAdjacentTaskFragment(root2);
+ root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether());
return TRANSACT_EFFECTS_LIFECYCLE;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bae5465..0b91742 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1473,12 +1473,12 @@
if (changing) {
mLastFreezeDuration = 0;
if (mWmService.mRoot.mOrientationChangeComplete
- && mDisplayContent.waitForUnfreeze(this)) {
+ && mDisplayContent.shouldSyncRotationChange(this)) {
mWmService.mRoot.mOrientationChangeComplete = false;
}
} else {
// The orientation change is completed. If it was hidden by the animation, reshow it.
- mDisplayContent.finishFadeRotationAnimation(this);
+ mDisplayContent.finishFadeRotationAnimation(mToken);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b147455..316051e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -566,7 +566,7 @@
if (w.getOrientationChanging()) {
if (!w.isDrawn()) {
- if (w.mDisplayContent.waitForUnfreeze(w)) {
+ if (w.mDisplayContent.shouldSyncRotationChange(w)) {
w.mWmService.mRoot.mOrientationChangeComplete = false;
mAnimator.mLastWindowFreezeSource = w;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 318ad06..e5a3b7a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -693,11 +693,8 @@
@Override
public String toString() {
if (stringName == null) {
- StringBuilder sb = new StringBuilder();
- sb.append("WindowToken{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" "); sb.append(token); sb.append('}');
- stringName = sb.toString();
+ stringName = "WindowToken{" + Integer.toHexString(System.identityHashCode(this))
+ + " type=" + windowType + " " + token + "}";
}
return stringName;
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 978eb1d..f72f2cc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -81,6 +81,7 @@
header_libs: [
"bionic_libc_platform_headers",
+ "bpf_connectivity_headers",
],
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 2ccef9a..4504853 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -55,6 +55,7 @@
#include "gnss/GnssAntennaInfoCallback.h"
#include "gnss/GnssBatching.h"
#include "gnss/GnssConfiguration.h"
+#include "gnss/GnssGeofence.h"
#include "gnss/GnssMeasurement.h"
#include "gnss/GnssNavigationMessage.h"
#include "gnss/Utils.h"
@@ -79,12 +80,6 @@
static jmethodID method_requestRefLocation;
static jmethodID method_requestSetID;
static jmethodID method_requestUtcTime;
-static jmethodID method_reportGeofenceTransition;
-static jmethodID method_reportGeofenceStatus;
-static jmethodID method_reportGeofenceAddStatus;
-static jmethodID method_reportGeofenceRemoveStatus;
-static jmethodID method_reportGeofencePauseStatus;
-static jmethodID method_reportGeofenceResumeStatus;
static jmethodID method_reportGnssServiceDied;
static jmethodID method_reportGnssPowerStats;
static jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
@@ -133,8 +128,6 @@
using android::hardware::gnss::V1_0::GnssLocationFlags;
using android::hardware::gnss::V1_0::IAGnssRilCallback;
-using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
-using android::hardware::gnss::V1_0::IGnssGeofencing;
using android::hardware::gnss::V1_0::IGnssNavigationMessage;
using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback;
using android::hardware::gnss::V1_0::IGnssNi;
@@ -192,8 +185,6 @@
using IGnssAidl = android::hardware::gnss::IGnss;
using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
-using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence;
-using IGnssGeofenceCallbackAidl = android::hardware::gnss::IGnssGeofenceCallback;
using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback;
using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration;
@@ -220,12 +211,10 @@
sp<IGnss_V2_1> gnssHal_V2_1 = nullptr;
sp<IGnssAidl> gnssHalAidl = nullptr;
sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr;
-sp<IGnssGeofenceAidl> gnssGeofenceAidlIface = nullptr;
sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr;
sp<IGnssXtra> gnssXtraIface = nullptr;
sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
-sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
sp<IAGnss_V1_0> agnssIface = nullptr;
sp<IAGnss_V2_0> agnssIface_V2_0 = nullptr;
sp<IGnssDebug_V1_0> gnssDebugIface = nullptr;
@@ -241,6 +230,7 @@
std::unique_ptr<android::gnss::GnssMeasurementInterface> gnssMeasurementIface = nullptr;
std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr;
std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr;
+std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr;
#define WAKE_LOCK_NAME "GPS"
@@ -714,199 +704,6 @@
return Void();
}
-/** Util class for GnssGeofenceCallback methods. */
-struct GnssGeofenceCallbackUtil {
- template <class T>
- static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition,
- int64_t timestampMillis);
- template <class T>
- static void gnssGeofenceStatusCb(int availability, const T& lastLocation);
- static void gnssGeofenceAddCb(int geofenceId, int status);
- static void gnssGeofenceRemoveCb(int geofenceId, int status);
- static void gnssGeofencePauseCb(int geofenceId, int status);
- static void gnssGeofenceResumeCb(int geofenceId, int status);
-
-private:
- GnssGeofenceCallbackUtil() = delete;
-};
-
-template <class T>
-void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
- int transition, int64_t timestamp) {
- JNIEnv* env = getJniEnv();
-
- jobject jLocation = translateGnssLocation(env, location);
-
- env->CallVoidMethod(mCallbacksObj,
- method_reportGeofenceTransition,
- geofenceId,
- jLocation,
- transition,
- timestamp);
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- env->DeleteLocalRef(jLocation);
-}
-
-template <class T>
-void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
- JNIEnv* env = getJniEnv();
-
- jobject jLocation = translateGnssLocation(env, lastLocation);
-
- env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- env->DeleteLocalRef(jLocation);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
- JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
- ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
- }
-
- env->CallVoidMethod(mCallbacksObj,
- method_reportGeofenceAddStatus,
- geofenceId,
- status);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
- JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
- ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
- }
-
- env->CallVoidMethod(mCallbacksObj,
- method_reportGeofenceRemoveStatus,
- geofenceId, status);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
- JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
- ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
- }
-
- env->CallVoidMethod(mCallbacksObj,
- method_reportGeofencePauseStatus,
- geofenceId, status);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
- JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
- ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
- }
-
- env->CallVoidMethod(mCallbacksObj,
- method_reportGeofenceResumeStatus,
- geofenceId, status);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-/*
- * GnssGeofenceCallbackAidl class implements the callback methods for the IGnssGeofence AIDL
- * interface.
- */
-struct GnssGeofenceCallbackAidl : public android::hardware::gnss::BnGnssGeofenceCallback {
- Status gnssGeofenceTransitionCb(int geofenceId, const GnssLocationAidl& location,
- int transition, int64_t timestampMillis) override;
- Status gnssGeofenceStatusCb(int availability, const GnssLocationAidl& lastLocation) override;
- Status gnssGeofenceAddCb(int geofenceId, int status) override;
- Status gnssGeofenceRemoveCb(int geofenceId, int status) override;
- Status gnssGeofencePauseCb(int geofenceId, int status) override;
- Status gnssGeofenceResumeCb(int geofenceId, int status) override;
-};
-
-Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId,
- const GnssLocationAidl& location,
- int transition, int64_t timestampMillis) {
- GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition,
- timestampMillis);
- return Status::ok();
-}
-
-Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability,
- const GnssLocationAidl& lastLocation) {
- GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation);
- return Status::ok();
-}
-
-Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) {
- GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status);
- return Status::ok();
-}
-
-Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) {
- GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status);
- return Status::ok();
-}
-
-Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) {
- GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status);
- return Status::ok();
-}
-
-Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) {
- GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status);
- return Status::ok();
-}
-
-/*
- * GnssGeofenceCallback class implements the callback methods for the
- * IGnssGeofence HIDL interface.
- */
-struct GnssGeofenceCallback : public IGnssGeofenceCallback {
- // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
- Return<void> gnssGeofenceTransitionCb(int32_t geofenceId, const GnssLocation_V1_0& location,
- GeofenceTransition transition,
- hardware::gnss::V1_0::GnssUtcTime timestamp) override;
- Return<void> gnssGeofenceStatusCb(GeofenceAvailability status,
- const GnssLocation_V1_0& location) override;
- Return<void> gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) override;
- Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) override;
- Return<void> gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) override;
- Return<void> gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) override;
-};
-
-Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
- int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition,
- hardware::gnss::V1_0::GnssUtcTime timestamp) {
- GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition,
- (int64_t)timestamp);
- return Void();
-}
-
-Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability availability,
- const GnssLocation_V1_0& location) {
- GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location);
- return Void();
-}
-
-Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) {
- GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status);
- return Void();
-}
-
-Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) {
- GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status);
- return Void();
-}
-
-Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) {
- GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status);
- return Void();
-}
-
-Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) {
- GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status);
- return Void();
-}
-
/*
* MeasurementCorrectionsCallback implements callback methods of interface
* IMeasurementCorrectionsCallback.hal.
@@ -1206,18 +1003,6 @@
method_requestRefLocation = env->GetMethodID(clazz, "requestRefLocation", "()V");
method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V");
method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
- method_reportGeofenceTransition = env->GetMethodID(clazz, "reportGeofenceTransition",
- "(ILandroid/location/Location;IJ)V");
- method_reportGeofenceStatus = env->GetMethodID(clazz, "reportGeofenceStatus",
- "(ILandroid/location/Location;)V");
- method_reportGeofenceAddStatus = env->GetMethodID(clazz, "reportGeofenceAddStatus",
- "(II)V");
- method_reportGeofenceRemoveStatus = env->GetMethodID(clazz, "reportGeofenceRemoveStatus",
- "(II)V");
- method_reportGeofenceResumeStatus = env->GetMethodID(clazz, "reportGeofenceResumeStatus",
- "(II)V");
- method_reportGeofencePauseStatus = env->GetMethodID(clazz, "reportGeofencePauseStatus",
- "(II)V");
method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
"(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
@@ -1522,27 +1307,27 @@
if (checkHidlReturn(gnssConfiguration,
"Unable to get a handle to GnssConfiguration_V1_1")) {
gnssConfigurationIface =
- std::make_unique<android::gnss::GnssConfiguration_V1_1>(gnssConfiguration);
+ std::make_unique<gnss::GnssConfiguration_V1_1>(gnssConfiguration);
}
} else {
auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration();
if (checkHidlReturn(gnssConfiguration,
"Unable to get a handle to GnssConfiguration_V1_0")) {
gnssConfigurationIface =
- std::make_unique<android::gnss::GnssConfiguration_V1_0>(gnssConfiguration);
+ std::make_unique<gnss::GnssConfiguration_V1_0>(gnssConfiguration);
}
}
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
- sp<IGnssGeofenceAidl> gnssGeofenceAidl;
- auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofenceAidl);
- if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence interface.")) {
- gnssGeofenceAidlIface = gnssGeofenceAidl;
+ sp<hardware::gnss::IGnssGeofence> gnssGeofence;
+ auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofence);
+ if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence AIDL interface.")) {
+ gnssGeofencingIface = std::make_unique<gnss::GnssGeofenceAidl>(gnssGeofence);
}
} else if (gnssHal != nullptr) {
auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
- gnssGeofencingIface = gnssGeofencing;
+ gnssGeofencingIface = std::make_unique<gnss::GnssGeofenceHidl>(gnssGeofencing);
}
}
@@ -1680,19 +1465,9 @@
ALOGI("Unable to initialize IAGnss interface.");
}
- // Set IGnssGeofencing.hal callback.
- if (gnssGeofenceAidlIface != nullptr) {
- sp<IGnssGeofenceCallbackAidl> gnssGeofenceCallbackAidl = new GnssGeofenceCallbackAidl();
- auto status = gnssGeofenceAidlIface->setCallback(gnssGeofenceCallbackAidl);
- if (!checkAidlStatus(status, "IGnssGeofenceAidl setCallback() failed.")) {
- gnssGeofenceAidlIface = nullptr;
- }
- } else if (gnssGeofencingIface != nullptr) {
- sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
- auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
- if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) {
- gnssGeofencingIface = nullptr;
- }
+ // Set GnssGeofence callback.
+ if (gnssGeofencingIface != nullptr) {
+ gnssGeofencingIface->setCallback(std::make_unique<gnss::GnssGeofenceCallback>());
} else {
ALOGI("Unable to initialize IGnssGeofencing interface.");
}
@@ -2239,7 +2014,7 @@
static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */,
jclass) {
- if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) {
+ if (gnssGeofencingIface == nullptr) {
return JNI_FALSE;
}
return JNI_TRUE;
@@ -2249,75 +2024,41 @@
JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude,
jdouble radius, jint last_transition, jint monitor_transition,
jint notification_responsiveness, jint unknown_timer) {
- if (gnssGeofenceAidlIface != nullptr) {
- auto status =
- gnssGeofenceAidlIface->addGeofence(geofenceId, latitude, longitude, radius,
- last_transition, monitor_transition,
- notification_responsiveness, unknown_timer);
- return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed.");
+ if (gnssGeofencingIface == nullptr) {
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
-
- if (gnssGeofencingIface != nullptr) {
- auto result = gnssGeofencingIface
- ->addGeofence(geofenceId, latitude, longitude, radius,
- static_cast<IGnssGeofenceCallback::GeofenceTransition>(
- last_transition),
- monitor_transition, notification_responsiveness,
- unknown_timer);
- return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
- }
-
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ return gnssGeofencingIface->addGeofence(geofenceId, latitude, longitude, radius,
+ last_transition, monitor_transition,
+ notification_responsiveness, unknown_timer);
}
static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass,
jint geofenceId) {
- if (gnssGeofenceAidlIface != nullptr) {
- auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId);
- return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
+ if (gnssGeofencingIface == nullptr) {
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
-
- if (gnssGeofencingIface != nullptr) {
- auto result = gnssGeofencingIface->removeGeofence(geofenceId);
- return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
- }
-
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ return gnssGeofencingIface->removeGeofence(geofenceId);
}
static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass,
jint geofenceId) {
- if (gnssGeofenceAidlIface != nullptr) {
- auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId);
- return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
+ if (gnssGeofencingIface == nullptr) {
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
-
- if (gnssGeofencingIface != nullptr) {
- auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
- return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
- }
-
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ return gnssGeofencingIface->pauseGeofence(geofenceId);
}
static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass,
jint geofenceId,
jint monitor_transition) {
- if (gnssGeofenceAidlIface != nullptr) {
- auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition);
- return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
+ if (gnssGeofencingIface == nullptr) {
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
-
- if (gnssGeofencingIface != nullptr) {
- auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
- return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
- }
-
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ return gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
}
static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env,
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index 6c6b304..ac50bfa 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -27,6 +27,8 @@
"GnssBatching.cpp",
"GnssBatchingCallback.cpp",
"GnssConfiguration.cpp",
+ "GnssGeofence.cpp",
+ "GnssGeofenceCallback.cpp",
"GnssMeasurement.cpp",
"GnssMeasurementCallback.cpp",
"GnssNavigationMessage.cpp",
diff --git a/services/core/jni/gnss/GnssGeofence.cpp b/services/core/jni/gnss/GnssGeofence.cpp
new file mode 100644
index 0000000..01d134d
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofence.cpp
@@ -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.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssGeofenceJni"
+
+#include "GnssGeofence.h"
+
+#include "Utils.h"
+
+using android::hardware::hidl_bitfield;
+using GeofenceTransition = android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceTransition;
+using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence;
+using IGnssGeofenceHidl = android::hardware::gnss::V1_0::IGnssGeofencing;
+
+namespace android::gnss {
+
+// Implementation of GnssGeofence (AIDL HAL)
+
+GnssGeofenceAidl::GnssGeofenceAidl(const sp<IGnssGeofenceAidl>& iGnssGeofence)
+ : mIGnssGeofenceAidl(iGnssGeofence) {
+ assert(mIGnssGeofenceAidl != nullptr);
+}
+
+jboolean GnssGeofenceAidl::setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) {
+ auto status = mIGnssGeofenceAidl->setCallback(callback->getAidl());
+ return checkAidlStatus(status, "IGnssGeofenceAidl init() failed.");
+}
+
+jboolean GnssGeofenceAidl::addGeofence(int geofenceId, double latitudeDegrees,
+ double longitudeDegrees, double radiusMeters,
+ int lastTransition, int monitorTransitions,
+ int notificationResponsivenessMs, int unknownTimerMs) {
+ auto status = mIGnssGeofenceAidl->addGeofence(geofenceId, latitudeDegrees, longitudeDegrees,
+ radiusMeters, lastTransition, monitorTransitions,
+ notificationResponsivenessMs, unknownTimerMs);
+ return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed");
+}
+
+jboolean GnssGeofenceAidl::removeGeofence(int geofenceId) {
+ auto status = mIGnssGeofenceAidl->removeGeofence(geofenceId);
+ return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
+}
+
+jboolean GnssGeofenceAidl::pauseGeofence(int geofenceId) {
+ auto status = mIGnssGeofenceAidl->pauseGeofence(geofenceId);
+ return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
+}
+
+jboolean GnssGeofenceAidl::resumeGeofence(int geofenceId, int monitorTransitions) {
+ auto status = mIGnssGeofenceAidl->resumeGeofence(geofenceId, monitorTransitions);
+ return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
+}
+
+// Implementation of GnssGeofenceHidl
+
+GnssGeofenceHidl::GnssGeofenceHidl(const sp<IGnssGeofenceHidl>& iGnssGeofence)
+ : mIGnssGeofenceHidl(iGnssGeofence) {
+ assert(mIGnssGeofenceHidl != nullptr);
+}
+
+jboolean GnssGeofenceHidl::setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) {
+ auto result = mIGnssGeofenceHidl->setCallback(callback->getHidl());
+ return checkHidlReturn(result, "IGnssGeofenceHidl setCallback() failed.");
+}
+
+jboolean GnssGeofenceHidl::addGeofence(int geofenceId, double latitudeDegrees,
+ double longitudeDegrees, double radiusMeters,
+ int lastTransition, int monitorTransitions,
+ int notificationResponsivenessMs, int unknownTimerMs) {
+ auto result = mIGnssGeofenceHidl->addGeofence(geofenceId, latitudeDegrees, longitudeDegrees,
+ radiusMeters,
+ static_cast<GeofenceTransition>(lastTransition),
+ static_cast<hidl_bitfield<GeofenceTransition>>(
+ monitorTransitions),
+ notificationResponsivenessMs, unknownTimerMs);
+ return checkHidlReturn(result, "IGnssGeofence addGeofence() failed.");
+}
+
+jboolean GnssGeofenceHidl::removeGeofence(int geofenceId) {
+ auto result = mIGnssGeofenceHidl->removeGeofence(geofenceId);
+ return checkHidlReturn(result, "IGnssGeofence removeGeofence() failed.");
+}
+
+jboolean GnssGeofenceHidl::pauseGeofence(int geofenceId) {
+ auto result = mIGnssGeofenceHidl->pauseGeofence(geofenceId);
+ return checkHidlReturn(result, "IGnssGeofence pauseGeofence() failed.");
+}
+
+jboolean GnssGeofenceHidl::resumeGeofence(int geofenceId, int monitorTransitions) {
+ auto result = mIGnssGeofenceHidl->resumeGeofence(geofenceId, monitorTransitions);
+ return checkHidlReturn(result, "IGnssGeofence resumeGeofence() failed.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssGeofence.h b/services/core/jni/gnss/GnssGeofence.h
new file mode 100644
index 0000000..31478ea
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofence.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSGEOFENCE_H
+#define _ANDROID_SERVER_GNSS_GNSSGEOFENCE_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssGeofencing.h>
+#include <android/hardware/gnss/BnGnssGeofence.h>
+#include <log/log.h>
+
+#include "GnssGeofenceCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class GnssGeofenceInterface {
+public:
+ virtual ~GnssGeofenceInterface() {}
+ virtual jboolean setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback);
+ virtual jboolean addGeofence(int geofenceId, double latitudeDegrees, double longitudeDegrees,
+ double radiusMeters, int lastTransition, int monitorTransitions,
+ int notificationResponsivenessMs, int unknownTimerMs);
+ virtual jboolean pauseGeofence(int geofenceId);
+ virtual jboolean resumeGeofence(int geofenceId, int monitorTransitions);
+ virtual jboolean removeGeofence(int geofenceId);
+};
+
+class GnssGeofenceAidl : public GnssGeofenceInterface {
+public:
+ GnssGeofenceAidl(const sp<android::hardware::gnss::IGnssGeofence>& iGnssGeofence);
+ jboolean setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) override;
+ jboolean addGeofence(int geofenceId, double latitudeDegrees, double longitudeDegrees,
+ double radiusMeters, int lastTransition, int monitorTransitions,
+ int notificationResponsivenessMs, int unknownTimerMs) override;
+ jboolean pauseGeofence(int geofenceId) override;
+ jboolean resumeGeofence(int geofenceId, int monitorTransitions) override;
+ jboolean removeGeofence(int geofenceId) override;
+
+private:
+ const sp<android::hardware::gnss::IGnssGeofence> mIGnssGeofenceAidl;
+};
+
+class GnssGeofenceHidl : public GnssGeofenceInterface {
+public:
+ GnssGeofenceHidl(const sp<android::hardware::gnss::V1_0::IGnssGeofencing>& iGnssGeofence);
+ jboolean setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) override;
+ jboolean addGeofence(int geofenceId, double latitudeDegrees, double longitudeDegrees,
+ double radiusMeters, int lastTransition, int monitorTransitions,
+ int notificationResponsivenessMs, int unknownTimerMs) override;
+ jboolean pauseGeofence(int geofenceId) override;
+ jboolean resumeGeofence(int geofenceId, int monitorTransitions) override;
+ jboolean removeGeofence(int geofenceId) override;
+
+private:
+ const sp<android::hardware::gnss::V1_0::IGnssGeofencing> mIGnssGeofenceHidl;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSGEOFENCE_H
diff --git a/services/core/jni/gnss/GnssGeofenceCallback.cpp b/services/core/jni/gnss/GnssGeofenceCallback.cpp
new file mode 100644
index 0000000..2cdf973
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofenceCallback.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GnssGeofenceCbJni"
+
+#include "GnssGeofenceCallback.h"
+
+namespace android::gnss {
+
+namespace {
+
+jmethodID method_reportGeofenceTransition;
+jmethodID method_reportGeofenceStatus;
+jmethodID method_reportGeofenceAddStatus;
+jmethodID method_reportGeofenceRemoveStatus;
+jmethodID method_reportGeofencePauseStatus;
+jmethodID method_reportGeofenceResumeStatus;
+
+} // anonymous namespace
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+using GeofenceAvailability =
+ android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceAvailability;
+using GeofenceStatus = android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus;
+using GeofenceTransition = android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceTransition;
+
+using GnssLocationAidl = android::hardware::gnss::GnssLocation;
+using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
+
+void GnssGeofence_class_init_once(JNIEnv* env, jclass clazz) {
+ method_reportGeofenceTransition = env->GetMethodID(clazz, "reportGeofenceTransition",
+ "(ILandroid/location/Location;IJ)V");
+ method_reportGeofenceStatus =
+ env->GetMethodID(clazz, "reportGeofenceStatus", "(ILandroid/location/Location;)V");
+ method_reportGeofenceAddStatus = env->GetMethodID(clazz, "reportGeofenceAddStatus", "(II)V");
+ method_reportGeofenceRemoveStatus =
+ env->GetMethodID(clazz, "reportGeofenceRemoveStatus", "(II)V");
+ method_reportGeofenceResumeStatus =
+ env->GetMethodID(clazz, "reportGeofenceResumeStatus", "(II)V");
+ method_reportGeofencePauseStatus =
+ env->GetMethodID(clazz, "reportGeofencePauseStatus", "(II)V");
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId,
+ const GnssLocationAidl& location,
+ int transition, int64_t timestampMillis) {
+ GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition,
+ timestampMillis);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability,
+ const GnssLocationAidl& lastLocation) {
+ GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status);
+ return Status::ok();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceTransitionCb(
+ int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition,
+ hardware::gnss::V1_0::GnssUtcTime timestamp) {
+ GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition,
+ (int64_t)timestamp);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceStatusCb(GeofenceAvailability availability,
+ const GnssLocation_V1_0& location) {
+ GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceAddCb(int32_t geofenceId,
+ GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceRemoveCb(int32_t geofenceId,
+ GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofencePauseCb(int32_t geofenceId,
+ GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceResumeCb(int32_t geofenceId,
+ GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status);
+ return Void();
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
+ JNIEnv* env = getJniEnv();
+ if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+ ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
+ }
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceAddStatus, geofenceId, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
+ JNIEnv* env = getJniEnv();
+ if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+ ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
+ }
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceRemoveStatus, geofenceId, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
+ JNIEnv* env = getJniEnv();
+ if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+ ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
+ }
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofencePauseStatus, geofenceId, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
+ JNIEnv* env = getJniEnv();
+ if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+ ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
+ }
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceResumeStatus, geofenceId, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssGeofenceCallback.h b/services/core/jni/gnss/GnssGeofenceCallback.h
new file mode 100644
index 0000000..b6a8a36
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofenceCallback.h
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSGEOFENCECALLBACK_H
+#define _ANDROID_SERVER_GNSS_GNSSGEOFENCECALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssGeofencing.h>
+#include <android/hardware/gnss/BnGnssGeofenceCallback.h>
+#include <log/log.h>
+
+#include <vector>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+extern jmethodID method_reportGeofenceTransition;
+extern jmethodID method_reportGeofenceStatus;
+extern jmethodID method_reportGeofenceAddStatus;
+extern jmethodID method_reportGeofenceRemoveStatus;
+extern jmethodID method_reportGeofencePauseStatus;
+extern jmethodID method_reportGeofenceResumeStatus;
+} // anonymous namespace
+
+void GnssGeofence_class_init_once(JNIEnv* env, jclass clazz);
+
+class GnssGeofenceCallbackAidl : public hardware::gnss::BnGnssGeofenceCallback {
+public:
+ GnssGeofenceCallbackAidl() {}
+ binder::Status gnssGeofenceTransitionCb(int geofenceId,
+ const hardware::gnss::GnssLocation& location,
+ int transition, int64_t timestampMillis) override;
+ binder::Status gnssGeofenceStatusCb(int availability,
+ const hardware::gnss::GnssLocation& lastLocation) override;
+ binder::Status gnssGeofenceAddCb(int geofenceId, int status) override;
+ binder::Status gnssGeofenceRemoveCb(int geofenceId, int status) override;
+ binder::Status gnssGeofencePauseCb(int geofenceId, int status) override;
+ binder::Status gnssGeofenceResumeCb(int geofenceId, int status) override;
+};
+
+class GnssGeofenceCallbackHidl : public hardware::gnss::V1_0::IGnssGeofenceCallback {
+public:
+ GnssGeofenceCallbackHidl() {}
+ hardware::Return<void> gnssGeofenceTransitionCb(
+ int32_t geofenceId, const hardware::gnss::V1_0::GnssLocation& location,
+ hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceTransition transition,
+ hardware::gnss::V1_0::GnssUtcTime timestamp) override;
+ hardware::Return<void> gnssGeofenceStatusCb(
+ hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceAvailability status,
+ const hardware::gnss::V1_0::GnssLocation& location) override;
+ hardware::Return<void> gnssGeofenceAddCb(
+ int32_t geofenceId,
+ hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+ hardware::Return<void> gnssGeofenceRemoveCb(
+ int32_t geofenceId,
+ hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+ hardware::Return<void> gnssGeofencePauseCb(
+ int32_t geofenceId,
+ hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+ hardware::Return<void> gnssGeofenceResumeCb(
+ int32_t geofenceId,
+ hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+};
+
+class GnssGeofenceCallback {
+public:
+ GnssGeofenceCallback() {}
+ sp<GnssGeofenceCallbackAidl> getAidl() {
+ if (callbackAidl == nullptr) {
+ callbackAidl = sp<GnssGeofenceCallbackAidl>::make();
+ }
+ return callbackAidl;
+ }
+
+ sp<GnssGeofenceCallbackHidl> getHidl() {
+ if (callbackHidl == nullptr) {
+ callbackHidl = sp<GnssGeofenceCallbackHidl>::make();
+ }
+ return callbackHidl;
+ }
+
+private:
+ sp<GnssGeofenceCallbackAidl> callbackAidl;
+ sp<GnssGeofenceCallbackHidl> callbackHidl;
+};
+
+/** Util class for GnssGeofenceCallback methods. */
+struct GnssGeofenceCallbackUtil {
+ template <class T>
+ static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition,
+ int64_t timestampMillis);
+ template <class T>
+ static void gnssGeofenceStatusCb(int availability, const T& lastLocation);
+ static void gnssGeofenceAddCb(int geofenceId, int status);
+ static void gnssGeofenceRemoveCb(int geofenceId, int status);
+ static void gnssGeofencePauseCb(int geofenceId, int status);
+ static void gnssGeofenceResumeCb(int geofenceId, int status);
+
+private:
+ GnssGeofenceCallbackUtil() = delete;
+};
+
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
+ int transition, int64_t timestamp) {
+ JNIEnv* env = getJniEnv();
+
+ jobject jLocation = translateGnssLocation(env, location);
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceTransition, geofenceId, jLocation,
+ transition, timestamp);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(jLocation);
+}
+
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
+ JNIEnv* env = getJniEnv();
+
+ jobject jLocation = translateGnssLocation(env, lastLocation);
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(jLocation);
+}
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSGEOFENCECALLBACK_H
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f605fe8..98a7b5e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1833,6 +1833,8 @@
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
loadOwners();
+
+ performPolicyVersionUpgrade();
}
/**
@@ -3159,7 +3161,6 @@
synchronized (getLockObject()) {
migrateUserRestrictionsIfNecessaryLocked();
fixupAutoTimeRestrictionDuringOrganizationOwnedDeviceMigration();
- performPolicyVersionUpgrade();
}
getUserData(UserHandle.USER_SYSTEM);
cleanUpOldUsers();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1ef9d13..50a0a68 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -1853,6 +1854,36 @@
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoAll_BoundByPersService_Cycle_Branch_Capability() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ bindService(app, client, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ bindService(client, client2, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ bindService(client2, app, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
+ MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
+ client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+ lru.clear();
+ lru.add(app);
+ lru.add(client);
+ lru.add(client2);
+ lru.add(client3);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability());
+ assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability());
+ assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoAll_Provider_Cycle_Branch_2() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 205c3da..74dd291 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -16,8 +16,8 @@
package com.android.server.accessibility;
-import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
-import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -82,7 +82,7 @@
@Test
public void getScale_fullscreenMode_expectedValue() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(TEST_SCALE).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -94,7 +94,7 @@
@Test
public void getScale_windowMode_expectedValue() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(WINDOW_MODE)
+ .setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(TEST_SCALE).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -106,7 +106,7 @@
@Test
public void getCenterX_canControlFullscreenMagnification_returnCenterX() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setCenterX(TEST_CENTER_X).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -119,7 +119,7 @@
@Test
public void getCenterX_canControlWindowMagnification_returnCenterX() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(WINDOW_MODE)
+ .setMode(MAGNIFICATION_MODE_WINDOW)
.setCenterX(TEST_CENTER_X).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -132,7 +132,7 @@
@Test
public void getCenterY_canControlFullscreenMagnification_returnCenterY() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setCenterY(TEST_CENTER_Y).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -145,7 +145,7 @@
@Test
public void getCenterY_canControlWindowMagnification_returnCenterY() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(WINDOW_MODE)
+ .setMode(MAGNIFICATION_MODE_WINDOW)
.setCenterY(TEST_CENTER_Y).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -158,7 +158,7 @@
@Test
public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
final Region region = new Region(10, 20, 100, 200);
- setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
region, /* canControlMagnification= */true);
@@ -169,7 +169,7 @@
@Test
public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
final Region region = new Region(10, 20, 100, 200);
- setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
region, /* canControlMagnification= */true);
@@ -180,7 +180,7 @@
@Test
public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
final Region region = new Region(10, 20, 100, 200);
- setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
doAnswer((invocation) -> {
((Region) invocation.getArguments()[1]).set(region);
return null;
@@ -198,7 +198,7 @@
@Test
public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setCenterX(TEST_CENTER_X).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -212,7 +212,7 @@
@Test
public void getMagnificationCenterY_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setCenterY(TEST_CENTER_Y).build();
setMagnificationActivated(TEST_DISPLAY, config);
@@ -225,17 +225,17 @@
@Test
public void getCurrentMode_configDefaultMode_returnActivatedMode() {
- final int targetMode = WINDOW_MODE;
+ final int targetMode = MAGNIFICATION_MODE_WINDOW;
setMagnificationActivated(TEST_DISPLAY, targetMode);
int currentMode = mMagnificationProcessor.getControllingMode(TEST_DISPLAY);
- assertEquals(WINDOW_MODE, currentMode);
+ assertEquals(MAGNIFICATION_MODE_WINDOW, currentMode);
}
@Test
public void reset_fullscreenMagnificationActivated() {
- setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
@@ -244,7 +244,7 @@
@Test
public void reset_windowMagnificationActivated() {
- setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
@@ -254,7 +254,7 @@
@Test
public void setMagnificationConfig_fullscreenModeNotRegistered_shouldRegister() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(TEST_SCALE)
.setCenterX(TEST_CENTER_X)
.setCenterY(TEST_CENTER_Y).build();
@@ -269,7 +269,7 @@
@Test
public void setMagnificationConfig_windowMode_enableMagnification() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(WINDOW_MODE)
+ .setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(TEST_SCALE)
.setCenterX(TEST_CENTER_X)
.setCenterY(TEST_CENTER_Y).build();
@@ -284,7 +284,7 @@
@Test
public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(FULLSCREEN_MODE)
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(TEST_SCALE)
.setCenterX(TEST_CENTER_X)
.setCenterY(TEST_CENTER_Y).build();
@@ -302,7 +302,7 @@
@Test
public void setMagnificationConfig_windowEnabled_expectedConfigValues() {
final MagnificationConfig config = new MagnificationConfig.Builder()
- .setMode(WINDOW_MODE)
+ .setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(TEST_SCALE)
.setCenterX(TEST_CENTER_X)
.setCenterY(TEST_CENTER_Y).build();
@@ -319,8 +319,8 @@
@Test
public void setMagnificationConfig_controllingModeChangeAndAnimating_transitionConfigMode() {
- final int currentActivatedMode = WINDOW_MODE;
- final int targetMode = FULLSCREEN_MODE;
+ final int currentActivatedMode = MAGNIFICATION_MODE_WINDOW;
+ final int targetMode = MAGNIFICATION_MODE_FULLSCREEN;
final MagnificationConfig oldConfig = new MagnificationConfig.Builder()
.setMode(currentActivatedMode)
.setScale(TEST_SCALE)
@@ -354,15 +354,15 @@
when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn(
true);
mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID);
- if (config.getMode() == FULLSCREEN_MODE) {
- when(mMockMagnificationController.isActivated(displayId, WINDOW_MODE)).thenReturn(
- false);
+ if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
+ when(mMockMagnificationController.isActivated(displayId,
+ MAGNIFICATION_MODE_WINDOW)).thenReturn(false);
mFullScreenMagnificationControllerStub.resetAndStubMethods();
mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
config.getCenterX(), config.getCenterY(), true, SERVICE_ID);
- } else if (config.getMode() == WINDOW_MODE) {
- when(mMockMagnificationController.isActivated(displayId, FULLSCREEN_MODE)).thenReturn(
- false);
+ } else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
+ when(mMockMagnificationController.isActivated(displayId,
+ MAGNIFICATION_MODE_FULLSCREEN)).thenReturn(false);
mWindowMagnificationManagerStub.resetAndStubMethods();
mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
config.getCenterX(), config.getCenterY());
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
new file mode 100644
index 0000000..41c7e31
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.Window;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link SideFpsEventHandler}.
+ * <p/>
+ * Run with <code>atest SideFpsEventHandlerTest</code>.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SideFpsEventHandlerTest {
+
+ private static final List<Integer> sAllStates = List.of(
+ FingerprintStateListener.STATE_IDLE,
+ FingerprintStateListener.STATE_ENROLLING,
+ FingerprintStateListener.STATE_KEYGUARD_AUTH,
+ FingerprintStateListener.STATE_BP_AUTH,
+ FingerprintStateListener.STATE_AUTH_OTHER);
+
+ @Rule
+ public TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getContext(), null);
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Spy
+ private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
+ @Mock
+ private AlertDialog mAlertDialog;
+ @Mock
+ private Window mWindow;
+
+ private TestLooper mLooper = new TestLooper();
+ private SideFpsEventHandler mEventHandler;
+ private FingerprintStateListener mFingerprintStateListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext.addMockSystemService(PackageManager.class, mPackageManager);
+ mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+
+ when(mDialogBuilder.create()).thenReturn(mAlertDialog);
+ when(mAlertDialog.getWindow()).thenReturn(mWindow);
+
+ mEventHandler = new SideFpsEventHandler(
+ mContext, new Handler(mLooper.getLooper()),
+ mContext.getSystemService(PowerManager.class), () -> mDialogBuilder);
+ }
+
+ @Test
+ public void ignoresWithoutFingerprintFeature() {
+ when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
+ .thenReturn(false);
+
+ assertThat(mEventHandler.onSinglePressDetected(60L)).isFalse();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).show();
+ }
+
+ @Test
+ public void ignoresWithoutSfps() throws Exception {
+ setupWithSensor(false /* hasSfps */, true /* initialized */);
+
+ for (int state : sAllStates) {
+ setFingerprintState(state);
+ assertThat(mEventHandler.onSinglePressDetected(200L)).isFalse();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).show();
+ }
+ }
+
+ @Test
+ public void ignoresWhileWaitingForSfps() throws Exception {
+ setupWithSensor(true /* hasSfps */, false /* initialized */);
+
+ for (int state : sAllStates) {
+ setFingerprintState(state);
+ assertThat(mEventHandler.onSinglePressDetected(400L)).isFalse();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).show();
+ }
+ }
+
+ @Test
+ public void ignoresWhenIdleOrUnknown() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setFingerprintState(FingerprintStateListener.STATE_IDLE);
+ assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+
+ setFingerprintState(FingerprintStateListener.STATE_AUTH_OTHER);
+ assertThat(mEventHandler.onSinglePressDetected(90000L)).isFalse();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).show();
+ }
+
+ @Test
+ public void ignoresOnKeyguard() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setFingerprintState(FingerprintStateListener.STATE_KEYGUARD_AUTH);
+ assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).show();
+ }
+
+ @Test
+ public void promptsWhenBPisActive() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setFingerprintState(FingerprintStateListener.STATE_BP_AUTH);
+ assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+ }
+
+ @Test
+ public void promptsWhenEnrolling() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setFingerprintState(FingerprintStateListener.STATE_ENROLLING);
+ assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+ }
+
+ private void setFingerprintState(@FingerprintStateListener.State int newState) {
+ if (mFingerprintStateListener != null) {
+ mFingerprintStateListener.onStateChanged(newState);
+ mLooper.dispatchAll();
+ }
+ }
+
+ private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
+ when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
+ .thenReturn(true);
+ when(mFingerprintManager.isPowerbuttonFps()).thenReturn(hasSfps);
+ mEventHandler.onFingerprintSensorReady();
+
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fpCallbackCaptor =
+ ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture());
+ if (initialized) {
+ fpCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ List.of(mock(FingerprintSensorPropertiesInternal.class)));
+ if (hasSfps) {
+ ArgumentCaptor<FingerprintStateListener> captor = ArgumentCaptor.forClass(
+ FingerprintStateListener.class);
+ verify(mFingerprintManager).registerFingerprintStateListener(captor.capture());
+ mFingerprintStateListener = captor.getValue();
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 6ee6020c..a963785 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -49,6 +49,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -112,6 +113,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -177,6 +179,7 @@
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -240,6 +243,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
new file mode 100644
index 0000000..9029ac5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.timezonedetector;
+
+import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+
+import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsTimeZoneSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+/** Tests for {@link MetricsTimeZoneDetectorState}. */
+public class MetricsTimeZoneDetectorStateTest {
+
+ private static final @UserIdInt int ARBITRARY_USER_ID = 1;
+ private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+ private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
+
+ private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
+ new ManualTimeZoneSuggestion("ManualTimeZoneId");
+
+ private static final TelephonyTimeZoneSuggestion TELEPHONY_TIME_ZONE_SUGGESTION =
+ new TelephonyTimeZoneSuggestion.Builder(0)
+ .setZoneId("TelephonyZoneId")
+ .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+ .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+ .build();
+
+ private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS,
+ Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+
+ private final OrdinalGenerator<String> mOrdinalGenerator =
+ new OrdinalGenerator<>(Function.identity());
+
+ @Test
+ public void enhancedMetricsCollectionEnabled() {
+ final boolean enhancedMetricsCollectionEnabled = true;
+ ConfigurationInternal configurationInternal =
+ createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+ // Create the object.
+ MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+ MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+ DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+ TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+ // Assert the content.
+ assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+ assertEquals(DEVICE_TIME_ZONE_ID, metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+ MetricsTimeZoneSuggestion expectedManualSuggestion =
+ MetricsTimeZoneSuggestion.createCertain(
+ new String[] { MANUAL_TIME_ZONE_SUGGESTION.getZoneId() },
+ new int[] { 1 });
+ assertEquals(expectedManualSuggestion,
+ metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+ MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+ MetricsTimeZoneSuggestion.createCertain(
+ new String[] { TELEPHONY_TIME_ZONE_SUGGESTION.getZoneId() },
+ new int[] { 2 });
+ assertEquals(expectedTelephonySuggestion,
+ metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+ MetricsTimeZoneSuggestion expectedGeoSuggestion =
+ MetricsTimeZoneSuggestion.createCertain(
+ GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+ new int[] { 3, 4 });
+ assertEquals(expectedGeoSuggestion,
+ metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+ }
+
+ @Test
+ public void enhancedMetricsCollectionDisabled() {
+ final boolean enhancedMetricsCollectionEnabled = false;
+ ConfigurationInternal configurationInternal =
+ createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+ // Create the object.
+ MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+ MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+ DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+ TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+ // Assert the content.
+ assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+ // When enhancedMetricsCollectionEnabled == false, no time zone IDs should be included.
+ assertNull(metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+ final String[] omittedZoneIds = null;
+
+ MetricsTimeZoneSuggestion expectedManualSuggestion =
+ MetricsTimeZoneSuggestion.createCertain(
+ omittedZoneIds,
+ new int[] { 1 });
+ assertEquals(expectedManualSuggestion,
+ metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+ MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+ MetricsTimeZoneSuggestion.createCertain(
+ omittedZoneIds,
+ new int[] { 2 });
+ assertEquals(expectedTelephonySuggestion,
+ metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+ MetricsTimeZoneSuggestion expectedGeoSuggestion =
+ MetricsTimeZoneSuggestion.createCertain(
+ omittedZoneIds,
+ new int[] { 3, 4 });
+ assertEquals(expectedGeoSuggestion,
+ metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+ }
+
+ private static void assertCommonConfiguration(ConfigurationInternal configurationInternal,
+ MetricsTimeZoneDetectorState metricsTimeZoneDetectorState) {
+ assertEquals(configurationInternal.isTelephonyDetectionSupported(),
+ metricsTimeZoneDetectorState.isTelephonyDetectionSupported());
+ assertEquals(configurationInternal.isGeoDetectionSupported(),
+ metricsTimeZoneDetectorState.isGeoDetectionSupported());
+ assertEquals(configurationInternal.getAutoDetectionEnabledSetting(),
+ metricsTimeZoneDetectorState.getAutoDetectionEnabledSetting());
+ assertEquals(configurationInternal.getLocationEnabledSetting(),
+ metricsTimeZoneDetectorState.getUserLocationEnabledSetting());
+ assertEquals(configurationInternal.getGeoDetectionEnabledSetting(),
+ metricsTimeZoneDetectorState.getGeoDetectionEnabledSetting());
+ assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(),
+ metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled());
+ assertEquals(0, metricsTimeZoneDetectorState.getDeviceTimeZoneIdOrdinal());
+ assertEquals(DETECTION_MODE_GEO, metricsTimeZoneDetectorState.getDetectionMode());
+ }
+
+ private static ConfigurationInternal createConfigurationInternal(
+ boolean enhancedMetricsCollectionEnabled) {
+ return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setUserConfigAllowed(true)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(true)
+ .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollectionEnabled)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 193b2e3..e0e5ba0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -379,6 +379,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.setLocationEnabledSetting(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index ef1b4f5..27f7814 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -87,10 +87,11 @@
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
- .setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -98,10 +99,11 @@
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
- .setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -109,10 +111,11 @@
private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
- .setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -120,10 +123,11 @@
private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
- .setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -134,6 +138,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
@@ -145,6 +150,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
@@ -955,8 +961,20 @@
}
@Test
- public void testGenerateMetricsState() {
- ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+ public void testGenerateMetricsState_enhancedMetricsCollection() {
+ testGenerateMetricsState(true);
+ }
+
+ @Test
+ public void testGenerateMetricsState_notEnhancedMetricsCollection() {
+ testGenerateMetricsState(false);
+ }
+
+ private void testGenerateMetricsState(boolean enhancedMetricsCollection) {
+ ConfigurationInternal expectedInternalConfig =
+ new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+ .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollection)
+ .build();
String expectedDeviceTimeZoneId = "InitialZoneId";
Script script = new Script()
@@ -1028,7 +1046,7 @@
tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
expectedManualSuggestion, expectedTelephonySuggestion,
expectedGeolocationTimeZoneSuggestion);
- // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons.
+ // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
assertEquals(expectedState, actualState);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index a2df3130..9d01310 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -47,6 +47,7 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
+ .setEnhancedMetricsCollectionEnabled(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index beee2a7..ab9fbb5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -52,6 +53,7 @@
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -124,10 +126,11 @@
new Handler(mTestLooper.getLooper()));
mVibrationSettings.onSystemReady();
+ // Simulate System defaults.
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
@After
@@ -142,13 +145,12 @@
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- verify(mListenerMock, times(8)).onChange();
+ verify(mListenerMock, times(7)).onChange();
}
@Test
@@ -173,126 +175,242 @@
verifyNoMoreInteractions(mListenerMock);
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
}
@Test
- public void shouldVibrateForRingerMode_beforeSystemReady_returnsFalseOnlyForRingtone() {
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setRingerMode(AudioManager.RINGER_MODE_MAX);
- VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
- new Handler(mTestLooper.getLooper()));
-
- assertFalse(vibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
- }
-
- @Test
- public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() {
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
- }
-
- @Test
- public void shouldVibrateForRingerMode_withVibrateWhenRinging_ignoreSettingsForSilentMode() {
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-
- setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_MAX);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
- }
-
- @Test
- public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
-
- setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_MAX);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
- }
-
- @Test
- public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-
- setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_MAX);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
- setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
- }
-
- @Test
- public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() {
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
+ public void shouldIgnoreVibration_fromBackground_doesNotIgnoreUsagesFromAllowlist() {
+ int[] expectedAllowedVibrations = new int[] {
+ USAGE_RINGTONE,
+ USAGE_ALARM,
+ USAGE_NOTIFICATION,
+ USAGE_COMMUNICATION_REQUEST,
+ USAGE_HARDWARE_FEEDBACK,
+ USAGE_PHYSICAL_EMULATION,
+ };
mVibrationSettings.mUidObserver.onUidStateChanged(
UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
- assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
+
+ for (int usage : expectedAllowedVibrations) {
+ assertNull("Error for usage " + VibrationAttributes.usageToString(usage),
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(usage)));
+ }
}
@Test
- public void shouldVibrateForUid_withBackgroundAllowedUsage_returnTrue() {
+ public void shouldIgnoreVibration_fromBackground_ignoresUsagesNotInAllowlist() {
+ int[] expectedIgnoredVibrations = new int[] {
+ USAGE_TOUCH,
+ USAGE_UNKNOWN,
+ };
+
mVibrationSettings.mUidObserver.onUidStateChanged(
UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_COMMUNICATION_REQUEST));
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_NOTIFICATION));
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_RINGTONE));
+ for (int usage : expectedIgnoredVibrations) {
+ assertEquals("Error for usage " + VibrationAttributes.usageToString(usage),
+ Vibration.Status.IGNORED_BACKGROUND,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(usage)));
+ }
}
@Test
- public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() {
+ public void shouldIgnoreVibration_fromForeground_allowsAnyUsage() {
+ mVibrationSettings.mUidObserver.onUidStateChanged(
+ UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_ALARM)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_inBatterySaverMode_doesNotIgnoreUsagesFromAllowlist() {
+ int[] expectedAllowedVibrations = new int[] {
+ USAGE_RINGTONE,
+ USAGE_ALARM,
+ USAGE_COMMUNICATION_REQUEST,
+ };
+
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE));
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST));
+ for (int usage : expectedAllowedVibrations) {
+ assertNull("Error for usage " + VibrationAttributes.usageToString(usage),
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(usage)));
+ }
}
@Test
- public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() {
+ public void shouldIgnoreVibration_inBatterySaverMode_ignoresUsagesNotInAllowlist() {
+ int[] expectedIgnoredVibrations = new int[] {
+ USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK,
+ USAGE_PHYSICAL_EMULATION,
+ USAGE_TOUCH,
+ USAGE_UNKNOWN,
+ };
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ for (int usage : expectedIgnoredVibrations) {
+ assertEquals("Error for usage " + VibrationAttributes.usageToString(usage),
+ Vibration.Status.IGNORED_FOR_POWER,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(usage)));
+ }
+ }
+
+ @Test
+ public void shouldIgnoreVibration_notInBatterySaverMode_allowsAnyUsage() {
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+ }
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+ @Test
+ public void shouldIgnoreVibration_withRingerModeSilent_ignoresRingtoneAndTouch() {
+ // Vibrating settings on are overruled by ringer mode.
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+ setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
- assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
+ assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withRingerModeVibrate_allowsAllVibrations() {
+ // Vibrating settings off are overruled by ringer mode.
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+ setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_PHYSICAL_EMULATION)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withRingerModeNormalAndRingSettingsOff_ignoresRingtoneOnly() {
+ // Vibrating settings off are respected for normal ringer mode.
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+ assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withRingerModeNormalAndRingSettingsOn_allowsAllVibrations() {
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_ALARM)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withRingerModeNormalAndRampingRingerOn_allowsAllVibrations() {
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withHapticFeedbackSettingsOff_ignoresTouchVibration() {
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withHardwareFeedbackSettingsOff_ignoresHardwareVibrations() {
+ setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_PHYSICAL_EMULATION)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withNotificationSettingsOff_ignoresNotificationVibrations() {
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_ALARM)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ }
+
+ @Test
+ public void shouldIgnoreVibration_withRingSettingsOff_ignoresRingtoneVibrations() {
+ // Vibrating settings on are overruled by ring intensity setting.
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
+ setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+ mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_ALARM)));
+ assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+ VibrationAttributes.createForUsage(USAGE_TOUCH)));
}
@Test
@@ -305,24 +423,6 @@
}
@Test
- public void isInZenMode_returnsSettingsValue() {
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
- assertFalse(mVibrationSettings.isInZenMode());
-
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_NO_INTERRUPTIONS);
- assertTrue(mVibrationSettings.isInZenMode());
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
- assertTrue(mVibrationSettings.isInZenMode());
-
- setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
- assertFalse(mVibrationSettings.isInZenMode());
-
- setGlobalSetting(Settings.Global.ZEN_MODE,
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- assertTrue(mVibrationSettings.isInZenMode());
- }
-
- @Test
public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() {
mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH);
@@ -464,12 +564,6 @@
mVibrationSettings.updateSettings();
}
- private void setGlobalSetting(String settingName, int value) {
- Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
- // FakeSettingsProvider don't support testing triggering ContentObserver yet.
- mVibrationSettings.updateSettings();
- }
-
private void setRingerMode(int ringerMode) {
mAudioManager.setRingerModeInternal(ringerMode);
assertEquals(ringerMode, mAudioManager.getRingerModeInternal());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 2e5cf3c..a9fd3c9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -840,21 +840,25 @@
when(mUm.getUsers()).thenReturn(userInfos);
// construct the permissions for each of them
- ArrayMap<Pair<Integer, String>, Boolean> permissions0 = new ArrayMap<>(),
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> permissions0 = new ArrayMap<>(),
permissions1 = new ArrayMap<>();
- permissions0.put(new Pair<>(10, "package1"), true);
- permissions0.put(new Pair<>(20, "package2"), false);
- permissions1.put(new Pair<>(11, "package1"), false);
- permissions1.put(new Pair<>(21, "package2"), true);
+ permissions0.put(new Pair<>(10, "package1"), new Pair<>(true, false));
+ permissions0.put(new Pair<>(20, "package2"), new Pair<>(false, true));
+ permissions1.put(new Pair<>(11, "package1"), new Pair<>(false, false));
+ permissions1.put(new Pair<>(21, "package2"), new Pair<>(true, true));
when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0);
when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1);
when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>());
- ArrayMap<Pair<Integer, String>, Boolean> combinedPermissions =
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> combinedPermissions =
mService.getAllUsersNotificationPermissions();
- assertTrue(combinedPermissions.get(new Pair<>(10, "package1")));
- assertFalse(combinedPermissions.get(new Pair<>(20, "package2")));
- assertFalse(combinedPermissions.get(new Pair<>(11, "package1")));
- assertTrue(combinedPermissions.get(new Pair<>(21, "package2")));
+ assertTrue(combinedPermissions.get(new Pair<>(10, "package1")).first);
+ assertFalse(combinedPermissions.get(new Pair<>(10, "package1")).second);
+ assertFalse(combinedPermissions.get(new Pair<>(20, "package2")).first);
+ assertTrue(combinedPermissions.get(new Pair<>(20, "package2")).second);
+ assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).first);
+ assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).second);
+ assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).first);
+ assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).second);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 5800400..bd3ba04 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -313,11 +313,22 @@
when(mPackageManager.getInstalledPackages(eq((long) GET_PERMISSIONS), anyInt()))
.thenReturn(requesting);
- Map<Pair<Integer, String>, Boolean> expected = ImmutableMap.of(new Pair(1, "first"), true,
- new Pair(2, "second"), true,
- new Pair(3, "third"), false);
+ // 2 and 3 are user-set permissions
+ when(mPermManager.getPermissionFlags(
+ "first", Manifest.permission.POST_NOTIFICATIONS, userId)).thenReturn(0);
+ when(mPermManager.getPermissionFlags(
+ "second", Manifest.permission.POST_NOTIFICATIONS, userId))
+ .thenReturn(FLAG_PERMISSION_USER_SET);
+ when(mPermManager.getPermissionFlags(
+ "third", Manifest.permission.POST_NOTIFICATIONS, userId))
+ .thenReturn(FLAG_PERMISSION_USER_SET);
- Map<Pair<Integer, String>, Boolean> actual =
+ Map<Pair<Integer, String>, Pair<Boolean, Boolean>> expected =
+ ImmutableMap.of(new Pair(1, "first"), new Pair(true, false),
+ new Pair(2, "second"), new Pair(true, true),
+ new Pair(3, "third"), new Pair(false, true));
+
+ Map<Pair<Integer, String>, Pair<Boolean, Boolean>> actual =
mPermissionHelper.getNotificationPermissionValues(userId);
assertThat(actual).containsExactlyEntriesIn(expected);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index dd6d469..c85e876 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -707,12 +707,12 @@
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true);
- appPermissions.put(new Pair(3, "third"), false);
- appPermissions.put(new Pair(UID_P, PKG_P), true);
- appPermissions.put(new Pair(UID_O, PKG_O), false);
- appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true);
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false));
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false));
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+ appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
.thenReturn(appPermissions);
@@ -788,12 +788,12 @@
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true);
- appPermissions.put(new Pair(3, "third"), false);
- appPermissions.put(new Pair(UID_P, PKG_P), true);
- appPermissions.put(new Pair(UID_O, PKG_O), false);
- appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true);
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false));
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false));
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+ appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
.thenReturn(appPermissions);
@@ -875,9 +875,9 @@
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(UID_P, PKG_P), true);
- appPermissions.put(new Pair(UID_O, PKG_O), false);
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
.thenReturn(appPermissions);
@@ -955,12 +955,12 @@
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true);
- appPermissions.put(new Pair(3, "third"), false);
- appPermissions.put(new Pair(UID_P, PKG_P), true);
- appPermissions.put(new Pair(UID_O, PKG_O), false);
- appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true);
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false));
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false));
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+ appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
.thenReturn(appPermissions);
@@ -2556,11 +2556,11 @@
// know about, those are ignored if migration is not enabled
// package permissions map to be passed in
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_P, PKG_P), true); // in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false)); // in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -2618,11 +2618,11 @@
// know about, those should still be included
// package permissions map to be passed in
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_P, PKG_P), true); // in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false)); // in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -2701,10 +2701,10 @@
// not from the passed-in permissions map
when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// package preferences: only PKG_P is banned
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2726,10 +2726,10 @@
// have their permission set to false, and not based on PackagePreferences importance
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// package preferences: PKG_O not banned based on local importance, and PKG_P is
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2770,10 +2770,10 @@
// confirm that the string resulting from dumpImpl contains only info from package prefs
when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, true)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// local package preferences: PKG_O is not banned even though the permissions would
// indicate so
@@ -2796,6 +2796,7 @@
ArrayList<String> notExpected = new ArrayList<>();
notExpected.add("first (1) importance=DEFAULT");
notExpected.add("third (3) importance=NONE");
+ notExpected.add("userSet="); // no user-set information pre migration
for (String exp : expected) {
assertTrue(actual.contains(exp));
@@ -2819,10 +2820,10 @@
// confirm that the string resulting from dumpImpl contains only importances from permission
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, true)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// local package preferences
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2837,9 +2838,9 @@
// expected (substring) output for each preference via permissions
ArrayList<String> expected = new ArrayList<>();
- expected.add("first (1) importance=DEFAULT");
- expected.add("third (3) importance=NONE");
- expected.add(PKG_O + " (" + UID_O + ") importance=NONE");
+ expected.add("first (1) importance=DEFAULT userSet=false");
+ expected.add("third (3) importance=NONE userSet=true");
+ expected.add(PKG_O + " (" + UID_O + ") importance=NONE userSet=false");
expected.add(PKG_P + " (" + UID_P + ")");
// make sure we don't have package preference info
@@ -2881,10 +2882,10 @@
// test that dumping to proto gets the importances from the right place
when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// local package preferences
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2921,10 +2922,10 @@
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
// permissions -- these should take precedence
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// local package preferences
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -4993,10 +4994,10 @@
when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
// build a collection of app permissions that should be passed in but ignored
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// package preferences: PKG_O not banned based on local importance, and PKG_P is
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -5036,10 +5037,10 @@
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
// build a collection of app permissions that should be passed in but ignored
- ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
- appPermissions.put(new Pair(1, "first"), true); // not in local prefs
- appPermissions.put(new Pair(3, "third"), false); // not in local prefs
- appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair(1, "first"), new Pair(true, false)); // not in local prefs
+ appPermissions.put(new Pair(3, "third"), new Pair(false, false)); // not in local prefs
+ appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
// package preferences: PKG_O not banned based on local importance, and PKG_P is
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7a133ea..4a8e121 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -39,6 +39,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.InsetsState.ITYPE_IME;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -128,6 +129,8 @@
import android.view.IRemoteAnimationRunner.Stub;
import android.view.IWindowManager;
import android.view.IWindowSession;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
@@ -3024,6 +3027,41 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+
+ InsetsSource imeSource = new InsetsSource(ITYPE_IME);
+ app.getInsetsState().addSource(imeSource);
+ mDisplayContent.setImeLayeringTarget(app);
+ mDisplayContent.updateImeInputAndControlTarget(app);
+
+ InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+ assertFalse(state.getSource(ITYPE_IME).isVisible());
+ assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty());
+
+ // Simulate app is closing and expect IME insets is frozen.
+ mDisplayContent.mOpeningApps.clear();
+ app.mActivityRecord.commitVisibility(false, false);
+ app.mActivityRecord.onWindowsGone();
+ assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Simulate app re-start input or turning screen off/on then unlocked by un-secure
+ // keyguard to back to the app, expect IME insets is not frozen
+ imeSource.setFrame(new Rect(100, 400, 500, 500));
+ app.getInsetsState().addSource(imeSource);
+ app.getInsetsState().setSourceVisible(ITYPE_IME, true);
+ mDisplayContent.updateImeInputAndControlTarget(app);
+ assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Verify when IME is visible and the app can receive the right IME insets from policy.
+ makeWindowVisibleAndDrawn(app, mImeWindow);
+ state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+ assertTrue(state.getSource(ITYPE_IME).isVisible());
+ assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
+ }
+
@Test
public void testInClosingAnimation_doNotHideSurface() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index c103bc6..3e617d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -19,6 +19,10 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -327,4 +331,16 @@
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
assertEquals("android.test.second", mInterceptor.mIntent.getAction());
}
+
+ @Test
+ public void testActivityLaunchedCallback_singleCallback() {
+ addMockInterceptorCallback(null);
+
+ assertEquals(1, mActivityInterceptorCallbacks.size());
+ final ActivityInterceptorCallback callback = mActivityInterceptorCallbacks.valueAt(0);
+ spyOn(callback);
+ mInterceptor.onActivityLaunched(null, null);
+
+ verify(callback, times(1)).onActivityLaunched(any(), any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d7a0ab3..dc0e028 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1336,7 +1336,6 @@
spyOn(rotationAnim);
// Assume that the display rotation is changed so it is frozen in preparation for animation.
doReturn(true).when(rotationAnim).hasScreenshot();
- mWm.mDisplayFrozen = true;
displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
displayContent.setRotationAnimation(rotationAnim);
// The fade rotation animation also starts to hide some non-app windows.
@@ -1347,9 +1346,9 @@
w.setOrientationChanging(true);
}
// The display only waits for the app window to unfreeze.
- assertFalse(displayContent.waitForUnfreeze(statusBar));
- assertFalse(displayContent.waitForUnfreeze(navBar));
- assertTrue(displayContent.waitForUnfreeze(app));
+ assertFalse(displayContent.shouldSyncRotationChange(statusBar));
+ assertFalse(displayContent.shouldSyncRotationChange(navBar));
+ assertTrue(displayContent.shouldSyncRotationChange(app));
// If all windows animated by fade rotation animation have done the orientation change,
// the animation controller should be cleared.
statusBar.setOrientationChanging(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2954d78..645d804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -535,6 +535,7 @@
mActivity.mVisibleRequested = false;
mActivity.visibleIgnoringKeyguard = false;
mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ mActivity.app.computeProcessActivityState();
// Simulate the display changes orientation.
final Configuration rotatedConfig = rotateDisplay(display, ROTATION_90);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index d68edba..cdf6b59 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -84,7 +84,7 @@
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -110,7 +110,7 @@
final Task adjacentRootTask = createTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
taskDisplayArea.setLaunchRootTask(rootTask,
new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -131,7 +131,7 @@
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a5c6dc0..9ad8c5b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -330,7 +330,7 @@
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
- mTransaction.setAdjacentRoots(mFragmentWindowToken, token2);
+ mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */);
assertThrows(SecurityException.class, () -> {
try {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 5fde7eb..a7a374b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -59,6 +59,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.clearInvocations;
@@ -1365,6 +1366,25 @@
assertNotNull(activity.getTask().getDimmer());
}
+ @Test
+ public void testMoveToFront_moveAdjacentTask() {
+ final Task task1 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final Task task2 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ spyOn(task2);
+
+ task1.setAdjacentTaskFragment(task2, false /* moveTogether */);
+ task1.moveToFront("" /* reason */);
+ verify(task2, never()).moveToFrontInner(anyString(), isNull());
+
+ // Reset adjacent tasks to move together.
+ task1.setAdjacentTaskFragment(null, false /* moveTogether */);
+ task1.setAdjacentTaskFragment(task2, true /* moveTogether */);
+ task1.moveToFront("" /* reason */);
+ verify(task2).moveToFrontInner(anyString(), isNull());
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d9a166a..b7417c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,6 +19,9 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -32,7 +35,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -44,6 +49,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.SurfaceControl;
+import android.view.TransactionCommittedListener;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
@@ -52,6 +58,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -469,6 +476,54 @@
}
@Test
+ public void testDisplayRotationChange() {
+ final Task task = createActivityRecord(mDisplayContent).getTask();
+ final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState[] windows = { statusBar, navBar, ime };
+ makeWindowVisible(windows);
+ mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
+ mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
+ mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */);
+ final FadeRotationAnimationController fadeController =
+ mDisplayContent.getFadeRotationAnimationController();
+ assertNotNull(fadeController);
+ for (WindowState w : windows) {
+ w.setOrientationChanging(true);
+ }
+ player.startTransition();
+
+ assertFalse(statusBar.mToken.inTransition());
+ assertTrue(ime.mToken.inTransition());
+ assertTrue(task.inTransition());
+
+ // Status bar finishes drawing before the start transaction. Its fade-in animation will be
+ // executed until the transaction is committed, so it is still in target tokens.
+ statusBar.setOrientationChanging(false);
+ assertTrue(fadeController.isTargetToken(statusBar.mToken));
+
+ final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ final ArgumentCaptor<TransactionCommittedListener> listenerCaptor =
+ ArgumentCaptor.forClass(TransactionCommittedListener.class);
+ player.onTransactionReady(startTransaction);
+
+ verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
+ // The transaction is committed, so fade-in animation for status bar is consumed.
+ listenerCaptor.getValue().onTransactionCommitted();
+ assertFalse(fadeController.isTargetToken(statusBar.mToken));
+
+ // Status bar finishes drawing after the start transaction, so its fade-in animation can
+ // execute directly.
+ navBar.setOrientationChanging(false);
+ assertFalse(fadeController.isTargetToken(navBar.mToken));
+ assertNull(mDisplayContent.getFadeRotationAnimationController());
+ }
+
+ @Test
public void testIntermediateVisibility() {
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
final TransitionController controller = new TransitionController(mAtm, snapshotController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1eed79f1..75a87ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -554,7 +554,7 @@
final RunningTaskInfo info2 = task2.getTaskInfo();
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setAdjacentRoots(info1.token, info2.token);
+ wct.setAdjacentRoots(info1.token, info2.token, false /* moveTogether */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(task1.getAdjacentTaskFragment(), task2);
assertEquals(task2.getAdjacentTaskFragment(), task1);
@@ -564,8 +564,8 @@
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
- task1.setAdjacentTaskFragment(null);
- task2.setAdjacentTaskFragment(null);
+ task1.setAdjacentTaskFragment(null, false /* moveTogether */);
+ task2.setAdjacentTaskFragment(null, false /* moveTogether */);
wct = new WindowContainerTransaction();
wct.clearLaunchAdjacentFlagRoot(info1.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 92fd682..a985de5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1660,10 +1660,17 @@
mLastRequest = request;
}
- public void start() {
+ void startTransition() {
mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
- mLastTransit.onTransactionReady(mLastTransit.getSyncId(),
- mock(SurfaceControl.Transaction.class));
+ }
+
+ void onTransactionReady(SurfaceControl.Transaction t) {
+ mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t);
+ }
+
+ void start() {
+ startTransition();
+ onTransactionReady(mock(SurfaceControl.Transaction.class));
}
public void finish() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index e5dc557..049966c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -489,8 +489,8 @@
createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
final WindowState splitWindow2 =
createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
- splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
- splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+ splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
+ splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
final Task aboveTask =
createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -528,8 +528,8 @@
createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
final WindowState splitWindow2 =
createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
- splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
- splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+ splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
+ splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
mDisplayContent.assignChildLayers(mTransaction);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a32bd2d..997c883 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -41,6 +41,7 @@
import android.app.IUidObserver;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
@@ -1554,6 +1555,52 @@
}
}
+ private void setEstimatedLaunchTime(int userId, String packageName,
+ @CurrentTimeMillisLong long estimatedLaunchTime) {
+ final long now = System.currentTimeMillis();
+ if (estimatedLaunchTime <= now) {
+ if (DEBUG) {
+ Slog.w(TAG, "Ignoring new estimate for "
+ + userId + ":" + packageName + " because it's old");
+ }
+ return;
+ }
+ final long oldEstimatedLaunchTime = mAppStandby.getEstimatedLaunchTime(packageName, userId);
+ if (estimatedLaunchTime != oldEstimatedLaunchTime) {
+ mAppStandby.setEstimatedLaunchTime(packageName, userId, estimatedLaunchTime);
+ mHandler.obtainMessage(
+ MSG_NOTIFY_ESTIMATED_LAUNCH_TIME_CHANGED, userId, 0, packageName)
+ .sendToTarget();
+ }
+ }
+
+ private void setEstimatedLaunchTimes(int userId, List<AppLaunchEstimateInfo> launchEstimates) {
+ final ArraySet<String> changedTimes = new ArraySet<>();
+ final long now = System.currentTimeMillis();
+ for (int i = launchEstimates.size() - 1; i >= 0; --i) {
+ AppLaunchEstimateInfo estimate = launchEstimates.get(i);
+ if (estimate.estimatedLaunchTime <= now) {
+ if (DEBUG) {
+ Slog.w(TAG, "Ignoring new estimate for "
+ + userId + ":" + estimate.packageName + " because it's old");
+ }
+ continue;
+ }
+ final long oldEstimatedLaunchTime =
+ mAppStandby.getEstimatedLaunchTime(estimate.packageName, userId);
+ if (estimate.estimatedLaunchTime != oldEstimatedLaunchTime) {
+ mAppStandby.setEstimatedLaunchTime(
+ estimate.packageName, userId, estimate.estimatedLaunchTime);
+ changedTimes.add(estimate.packageName);
+ }
+ }
+ if (changedTimes.size() > 0) {
+ mHandler.obtainMessage(
+ MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED, userId, 0, changedTimes)
+ .sendToTarget();
+ }
+ }
+
/**
* Called via the local interface.
*/
@@ -2293,6 +2340,37 @@
}
@Override
+ public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
+ int userId) {
+ getContext().enforceCallingPermission(
+ Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE,
+ "No permission to change app launch estimates");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this
+ .setEstimatedLaunchTime(userId, packageName, estimatedLaunchTime);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
+ getContext().enforceCallingPermission(
+ Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE,
+ "No permission to change app launch estimates");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this
+ .setEstimatedLaunchTimes(userId, estimatedLaunchTimes.getList());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void onCarrierPrivilegedAppsChanged() {
if (DEBUG) {
Slog.i(TAG, "Carrier privileged apps changed");
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 3c799e0..8c78f74 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -101,6 +101,25 @@
}
}
+ /**
+ * Convenience method for running the provided action in the provided
+ * executor enclosed in
+ * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+ *
+ * Any exception thrown by the given action will need to be handled by caller.
+ *
+ */
+ public static void runWithCleanCallingIdentity(
+ @NonNull Runnable action, @NonNull Executor executor) {
+ if (action != null) {
+ if (executor != null) {
+ executor.execute(() -> runWithCleanCallingIdentity(action));
+ } else {
+ runWithCleanCallingIdentity(action);
+ }
+ }
+ }
+
/**
* Convenience method for running the provided action enclosed in
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ae2facd..d120f5a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12620,16 +12620,19 @@
* <p>If this object has been created with {@link #createForSubscriptionId}, applies
* to the given subId. Otherwise, applies to
* {@link SubscriptionManager#getDefaultDataSubscriptionId()}
- *
* @param reason the reason the data enable change is taking place
* @return whether data is enabled for a reason.
* <p>Requires Permission:
+ * The calling app has carrier privileges (see {@link #hasCarrierPrivileges}) or
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} or
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE}
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
- android.Manifest.permission.READ_PHONE_STATE})
+ android.Manifest.permission.READ_PHONE_STATE,
+ android.Manifest.permission.MODIFY_PHONE_STATE
+ })
public boolean isDataEnabledForReason(@DataEnabledReason int reason) {
return isDataEnabledForReason(getSubId(), reason);
}
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 097d363..8c02ffe 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -328,8 +328,6 @@
@SystemApi
public static final String TYPE_XCAP_STRING = "xcap";
-
-
/**
* APN type for Virtual SIM service.
*
@@ -506,27 +504,21 @@
private final int mRoamingProtocol;
private final int mMtuV4;
private final int mMtuV6;
-
private final boolean mCarrierEnabled;
-
- private final int mNetworkTypeBitmask;
-
+ private final @TelephonyManager.NetworkTypeBitMask int mNetworkTypeBitmask;
+ private final @TelephonyManager.NetworkTypeBitMask long mLingeringNetworkTypeBitmask;
private final int mProfileId;
-
private final boolean mPersistent;
private final int mMaxConns;
private final int mWaitTime;
private final int mMaxConnsTime;
-
private final int mMvnoType;
private final String mMvnoMatchData;
-
private final int mApnSetId;
-
private boolean mPermanentFailed = false;
private final int mCarrierId;
-
private final int mSkip464Xlat;
+ private final boolean mAlwaysOn;
/**
* Returns the MTU size of the IPv4 mobile interface to which the APN connected. Note this value
@@ -843,20 +835,37 @@
}
/**
- * Returns a bitmask describing the Radio Technologies(Network Types) which this APN may use.
+ * Returns a bitmask describing the Radio Technologies (Network Types) which this APN may use.
*
* NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}.
*
* Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN},
* {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}.
*
- * @return a bitmask describing the Radio Technologies(Network Types)
+ * @return a bitmask describing the Radio Technologies (Network Types) or 0 if it is undefined.
*/
public int getNetworkTypeBitmask() {
return mNetworkTypeBitmask;
}
/**
+ * Returns a bitmask describing the Radio Technologies (Network Types) that should not be torn
+ * down if it exists or brought up if it already exists for this APN.
+ *
+ * NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}.
+ *
+ * Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN},
+ * {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}.
+ *
+ * @return a bitmask describing the Radio Technologies (Network Types) that should linger
+ * or 0 if it is undefined.
+ * @hide
+ */
+ public @TelephonyManager.NetworkTypeBitMask long getLingeringNetworkTypeBitmask() {
+ return mLingeringNetworkTypeBitmask;
+ }
+
+ /**
* Returns the MVNO match type for this APN.
*
* @see Builder#setMvnoType(int)
@@ -888,6 +897,18 @@
return mSkip464Xlat;
}
+ /**
+ * Returns whether User Plane resources have to be activated during every transition from
+ * CM-IDLE mode to CM-CONNECTED state for this APN
+ * See 3GPP TS 23.501 section 5.6.13
+ *
+ * @return True if the PDU session for this APN should always be on and false otherwise
+ * @hide
+ */
+ public boolean isAlwaysOn() {
+ return mAlwaysOn;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -912,6 +933,7 @@
this.mMtuV6 = builder.mMtuV6;
this.mCarrierEnabled = builder.mCarrierEnabled;
this.mNetworkTypeBitmask = builder.mNetworkTypeBitmask;
+ this.mLingeringNetworkTypeBitmask = builder.mLingeringNetworkTypeBitmask;
this.mProfileId = builder.mProfileId;
this.mPersistent = builder.mModemCognitive;
this.mMaxConns = builder.mMaxConns;
@@ -922,6 +944,7 @@
this.mApnSetId = builder.mApnSetId;
this.mCarrierId = builder.mCarrierId;
this.mSkip464Xlat = builder.mSkip464Xlat;
+ this.mAlwaysOn = builder.mAlwaysOn;
}
/**
@@ -938,6 +961,10 @@
networkTypeBitmask =
ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
}
+ int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));
+ if (mtuV4 == -1) {
+ mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU));
+ }
return new Builder()
.setId(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)))
@@ -972,6 +999,8 @@
.setCarrierEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
Telephony.Carriers.CARRIER_ENABLED)) == 1)
.setNetworkTypeBitmask(networkTypeBitmask)
+ .setLingeringNetworkTypeBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+ Carriers.LINGERING_NETWORK_TYPE_BITMASK)))
.setProfileId(cursor.getInt(
cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)))
.setModemCognitive(cursor.getInt(cursor.getColumnIndexOrThrow(
@@ -982,8 +1011,8 @@
cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME_RETRY)))
.setMaxConnsTime(cursor.getInt(cursor.getColumnIndexOrThrow(
Telephony.Carriers.TIME_LIMIT_FOR_MAX_CONNECTIONS)))
- .setMtuV4(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)))
- .setMtuV6(UNSET_MTU) // TODO: Add corresponding support in telephony provider
+ .setMtuV4(mtuV4)
+ .setMtuV6(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V6)))
.setMvnoType(getMvnoTypeIntFromString(
cursor.getString(cursor.getColumnIndexOrThrow(
Telephony.Carriers.MVNO_TYPE))))
@@ -994,6 +1023,7 @@
.setCarrierId(cursor.getInt(
cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
.setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
+ .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
.buildWithoutCheck();
}
@@ -1019,6 +1049,7 @@
.setRoamingProtocol(apn.mRoamingProtocol)
.setCarrierEnabled(apn.mCarrierEnabled)
.setNetworkTypeBitmask(apn.mNetworkTypeBitmask)
+ .setLingeringNetworkTypeBitmask(apn.mLingeringNetworkTypeBitmask)
.setProfileId(apn.mProfileId)
.setModemCognitive(apn.mPersistent)
.setMaxConns(apn.mMaxConns)
@@ -1031,6 +1062,7 @@
.setApnSetId(apn.mApnSetId)
.setCarrierId(apn.mCarrierId)
.setSkip464Xlat(apn.mSkip464Xlat)
+ .setAlwaysOn(apn.mAlwaysOn)
.buildWithoutCheck();
}
@@ -1069,9 +1101,11 @@
sb.append(", ").append(mMvnoMatchData);
sb.append(", ").append(mPermanentFailed);
sb.append(", ").append(mNetworkTypeBitmask);
+ sb.append(", ").append(mLingeringNetworkTypeBitmask);
sb.append(", ").append(mApnSetId);
sb.append(", ").append(mCarrierId);
sb.append(", ").append(mSkip464Xlat);
+ sb.append(", ").append(mAlwaysOn);
return sb.toString();
}
@@ -1136,8 +1170,9 @@
return Objects.hash(mApnName, mProxyAddress, mProxyPort, mMmsc, mMmsProxyAddress,
mMmsProxyPort, mUser, mPassword, mAuthType, mApnTypeBitmask, mId, mOperatorNumeric,
mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
- mProfileId, mPersistent, mMaxConns, mWaitTime, mMaxConnsTime, mMvnoType,
- mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat);
+ mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
+ mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
+ mAlwaysOn);
}
@Override
@@ -1174,9 +1209,11 @@
&& Objects.equals(mMvnoType, other.mMvnoType)
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
&& Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+ && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
&& Objects.equals(mApnSetId, other.mApnSetId)
&& Objects.equals(mCarrierId, other.mCarrierId)
- && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
+ && Objects.equals(mAlwaysOn, other.mAlwaysOn);
}
/**
@@ -1210,6 +1247,7 @@
&& Objects.equals(mPassword, other.mPassword)
&& Objects.equals(mAuthType, other.mAuthType)
&& Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+ && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
&& (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
&& (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
&& Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
@@ -1224,7 +1262,8 @@
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
&& Objects.equals(mApnSetId, other.mApnSetId)
&& Objects.equals(mCarrierId, other.mCarrierId)
- && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
+ && Objects.equals(mAlwaysOn, other.mAlwaysOn);
}
/**
@@ -1304,9 +1343,13 @@
apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled);
apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
+ apnValue.put(Telephony.Carriers.LINGERING_NETWORK_TYPE_BITMASK,
+ mLingeringNetworkTypeBitmask);
+ apnValue.put(Telephony.Carriers.MTU_V4, mMtuV4);
+ apnValue.put(Telephony.Carriers.MTU_V6, mMtuV6);
apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
-
+ apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
return apnValue;
}
@@ -1510,6 +1553,31 @@
return ServiceState.bitmaskHasTech(mNetworkTypeBitmask, networkType);
}
+ /**
+ * Check if this APN setting can support the given lingering network
+ *
+ * @param networkType The lingering network type
+ * @return {@code true} if this APN setting can support the given lingering network.
+ *
+ * @hide
+ */
+ public boolean canSupportLingeringNetworkType(@NetworkType int networkType) {
+ if (networkType == 0) {
+ return canSupportNetworkType(networkType);
+ }
+ // Do a special checking for GSM. In reality, GSM is a voice only network type and can never
+ // be used for data. We allow it here because in some DSDS corner cases, on the non-DDS
+ // sub, modem reports data rat unknown. In that case if voice is GSM and this APN supports
+ // GPRS or EDGE, this APN setting should be selected.
+ if (networkType == TelephonyManager.NETWORK_TYPE_GSM
+ && (mLingeringNetworkTypeBitmask & (TelephonyManager.NETWORK_TYPE_BITMASK_GPRS
+ | TelephonyManager.NETWORK_TYPE_BITMASK_EDGE)) != 0) {
+ return true;
+ }
+
+ return ServiceState.bitmaskHasTech((int) mLingeringNetworkTypeBitmask, networkType);
+ }
+
// Implement Parcelable.
@Override
/** @hide */
@@ -1537,6 +1605,7 @@
dest.writeInt(mRoamingProtocol);
dest.writeBoolean(mCarrierEnabled);
dest.writeInt(mNetworkTypeBitmask);
+ dest.writeLong(mLingeringNetworkTypeBitmask);
dest.writeInt(mProfileId);
dest.writeBoolean(mPersistent);
dest.writeInt(mMaxConns);
@@ -1549,6 +1618,7 @@
dest.writeInt(mApnSetId);
dest.writeInt(mCarrierId);
dest.writeInt(mSkip464Xlat);
+ dest.writeBoolean(mAlwaysOn);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1570,6 +1640,7 @@
.setRoamingProtocol(in.readInt())
.setCarrierEnabled(in.readBoolean())
.setNetworkTypeBitmask(in.readInt())
+ .setLingeringNetworkTypeBitmask(in.readLong())
.setProfileId(in.readInt())
.setModemCognitive(in.readBoolean())
.setMaxConns(in.readInt())
@@ -1582,6 +1653,7 @@
.setApnSetId(in.readInt())
.setCarrierId(in.readInt())
.setSkip464Xlat(in.readInt())
+ .setAlwaysOn(in.readBoolean())
.buildWithoutCheck();
}
@@ -1649,7 +1721,8 @@
private int mRoamingProtocol = UNSPECIFIED_INT;
private int mMtuV4;
private int mMtuV6;
- private int mNetworkTypeBitmask;
+ private @TelephonyManager.NetworkTypeBitMask int mNetworkTypeBitmask;
+ private @TelephonyManager.NetworkTypeBitMask long mLingeringNetworkTypeBitmask;
private boolean mCarrierEnabled;
private int mProfileId;
private boolean mModemCognitive;
@@ -1661,6 +1734,7 @@
private int mApnSetId;
private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
+ private boolean mAlwaysOn;
/**
* Default constructor for Builder.
@@ -2012,6 +2086,19 @@
}
/**
+ * Sets lingering Radio Technology (Network Type) for this APN.
+ *
+ * @param lingeringNetworkTypeBitmask the Radio Technology (Network Type) that should linger
+ * @hide
+ */
+ @NonNull
+ public Builder setLingeringNetworkTypeBitmask(@TelephonyManager.NetworkTypeBitMask
+ long lingeringNetworkTypeBitmask) {
+ this.mLingeringNetworkTypeBitmask = lingeringNetworkTypeBitmask;
+ return this;
+ }
+
+ /**
* Sets the MVNO match type for this APN.
*
* @param mvnoType the MVNO match type to set for this APN
@@ -2048,6 +2135,18 @@
}
/**
+ * Sets whether the PDU session brought up by this APN should always be on.
+ * See 3GPP TS 23.501 section 5.6.13
+ *
+ * @param alwaysOn the always on status to set for this APN
+ * @hide
+ */
+ public Builder setAlwaysOn(boolean alwaysOn) {
+ this.mAlwaysOn = alwaysOn;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index dfe5e6c9..6569de6 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -27,10 +27,12 @@
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* Provides the call initiation/termination, and media exchange between two IMS endpoints.
@@ -522,6 +524,7 @@
private final IImsCallSession miSession;
private boolean mClosed = false;
private Listener mListener;
+ private Executor mListenerExecutor = Runnable::run;
/** @hide */
public ImsCallSession(IImsCallSession iSession) {
@@ -538,9 +541,9 @@
}
/** @hide */
- public ImsCallSession(IImsCallSession iSession, Listener listener) {
+ public ImsCallSession(IImsCallSession iSession, Listener listener, Executor executor) {
this(iSession);
- setListener(listener);
+ setListener(listener, executor);
}
/**
@@ -738,10 +741,14 @@
* override the previous listener.
*
* @param listener to listen to the session events of this object
+ * @param executor an Executor that will execute callbacks
* @hide
*/
- public void setListener(Listener listener) {
+ public void setListener(Listener listener, Executor executor) {
mListener = listener;
+ if (executor != null) {
+ mListenerExecutor = executor;
+ }
}
/**
@@ -1206,42 +1213,48 @@
@Override
public void callSessionInitiating(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionInitiating(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating(
+ ImsCallSession.this, profile), mListenerExecutor);
}
}
@Override
public void callSessionProgressing(ImsStreamMediaProfile profile) {
if (mListener != null) {
- mListener.callSessionProgressing(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing(
+ ImsCallSession.this, profile), mListenerExecutor);
}
}
@Override
public void callSessionInitiated(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionStarted(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted(
+ ImsCallSession.this, profile), mListenerExecutor);
}
}
@Override
public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+ ImsCallSession.this, reasonInfo), mListenerExecutor);
}
}
@Override
public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+ ImsCallSession.this, reasonInfo), mListenerExecutor);
}
}
@Override
public void callSessionTerminated(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated(
+ ImsCallSession.this, reasonInfo), mListenerExecutor);
}
}
@@ -1251,42 +1264,49 @@
@Override
public void callSessionHeld(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionHeld(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld(
+ ImsCallSession.this, profile), mListenerExecutor);
}
}
@Override
public void callSessionHoldFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed(
+ ImsCallSession.this, reasonInfo), mListenerExecutor);
}
}
@Override
public void callSessionHoldReceived(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived(
+ ImsCallSession.this, profile), mListenerExecutor);
}
}
@Override
public void callSessionResumed(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionResumed(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed(
+ ImsCallSession.this, profile), mListenerExecutor);
}
}
@Override
public void callSessionResumeFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed(
+ ImsCallSession.this, reasonInfo), mListenerExecutor);
}
}
@Override
public void callSessionResumeReceived(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionResumeReceived(ImsCallSession.this, profile),
+ mListenerExecutor);
}
}
@@ -1311,13 +1331,15 @@
@Override
public void callSessionMergeComplete(IImsCallSession newSession) {
if (mListener != null) {
- if (newSession != null) {
- // New session created after conference
- mListener.callSessionMergeComplete(new ImsCallSession(newSession));
- } else {
- // Session already exists. Hence no need to pass
- mListener.callSessionMergeComplete(null);
- }
+ TelephonyUtils.runWithCleanCallingIdentity(()-> {
+ if (newSession != null) {
+ // New session created after conference
+ mListener.callSessionMergeComplete(new ImsCallSession(newSession));
+ } else {
+ // Session already exists. Hence no need to pass
+ mListener.callSessionMergeComplete(null);
+ }
+ }, mListenerExecutor);
}
}
@@ -1329,7 +1351,9 @@
@Override
public void callSessionMergeFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo),
+ mListenerExecutor);
}
}
@@ -1339,21 +1363,27 @@
@Override
public void callSessionUpdated(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionUpdated(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionUpdated(ImsCallSession.this, profile),
+ mListenerExecutor);
}
}
@Override
public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo),
+ mListenerExecutor);
}
}
@Override
public void callSessionUpdateReceived(ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionUpdateReceived(ImsCallSession.this, profile),
+ mListenerExecutor);
}
}
@@ -1364,15 +1394,18 @@
public void callSessionConferenceExtended(IImsCallSession newSession,
ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionConferenceExtended(ImsCallSession.this,
- new ImsCallSession(newSession), profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionConferenceExtended(ImsCallSession.this,
+ new ImsCallSession(newSession), profile), mListenerExecutor);
}
}
@Override
public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionConferenceExtendFailed(
+ ImsCallSession.this, reasonInfo), mListenerExecutor);
}
}
@@ -1380,8 +1413,9 @@
public void callSessionConferenceExtendReceived(IImsCallSession newSession,
ImsCallProfile profile) {
if (mListener != null) {
- mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
- new ImsCallSession(newSession), profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+ new ImsCallSession(newSession), profile), mListenerExecutor);
}
}
@@ -1392,30 +1426,36 @@
@Override
public void callSessionInviteParticipantsRequestDelivered() {
if (mListener != null) {
- mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionInviteParticipantsRequestDelivered(
+ ImsCallSession.this), mListenerExecutor);
}
}
@Override
public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
- reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+ reasonInfo), mListenerExecutor);
}
}
@Override
public void callSessionRemoveParticipantsRequestDelivered() {
if (mListener != null) {
- mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRemoveParticipantsRequestDelivered(
+ ImsCallSession.this), mListenerExecutor);
}
}
@Override
public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
- reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+ reasonInfo), mListenerExecutor);
}
}
@@ -1425,7 +1465,9 @@
@Override
public void callSessionConferenceStateUpdated(ImsConferenceState state) {
if (mListener != null) {
- mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state),
+ mListenerExecutor);
}
}
@@ -1435,7 +1477,9 @@
@Override
public void callSessionUssdMessageReceived(int mode, String ussdMessage) {
if (mListener != null) {
- mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode,
+ ussdMessage), mListenerExecutor);
}
}
@@ -1453,8 +1497,9 @@
@Override
public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) {
if (mListener != null) {
- mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
- targetNetworkType);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
+ targetNetworkType), mListenerExecutor);
}
}
@@ -1465,8 +1510,9 @@
public void callSessionHandover(int srcNetworkType, int targetNetworkType,
ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
- targetNetworkType, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
+ targetNetworkType, reasonInfo), mListenerExecutor);
}
}
@@ -1477,8 +1523,9 @@
public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
- targetNetworkType, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
+ targetNetworkType, reasonInfo), mListenerExecutor);
}
}
@@ -1488,7 +1535,9 @@
@Override
public void callSessionTtyModeReceived(int mode) {
if (mListener != null) {
- mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionTtyModeReceived(ImsCallSession.this, mode),
+ mListenerExecutor);
}
}
@@ -1500,14 +1549,18 @@
*/
public void callSessionMultipartyStateChanged(boolean isMultiParty) {
if (mListener != null) {
- mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionMultipartyStateChanged(ImsCallSession.this,
+ isMultiParty), mListenerExecutor);
}
}
@Override
public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) {
if (mListener != null) {
- mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionSuppServiceReceived(ImsCallSession.this,
+ suppServiceInfo), mListenerExecutor);
}
}
@@ -1517,7 +1570,9 @@
@Override
public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) {
if (mListener != null) {
- mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRttModifyRequestReceived(ImsCallSession.this,
+ callProfile), mListenerExecutor);
}
}
@@ -1527,7 +1582,9 @@
@Override
public void callSessionRttModifyResponseReceived(int status) {
if (mListener != null) {
- mListener.callSessionRttModifyResponseReceived(status);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRttModifyResponseReceived(status),
+ mListenerExecutor);
}
}
@@ -1537,7 +1594,8 @@
@Override
public void callSessionRttMessageReceived(String rttMessage) {
if (mListener != null) {
- mListener.callSessionRttMessageReceived(rttMessage);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor);
}
}
@@ -1547,21 +1605,26 @@
@Override
public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
if (mListener != null) {
- mListener.callSessionRttAudioIndicatorChanged(profile);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRttAudioIndicatorChanged(profile),
+ mListenerExecutor);
}
}
@Override
public void callSessionTransferred() {
if (mListener != null) {
- mListener.callSessionTransferred(ImsCallSession.this);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor);
}
}
@Override
public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
if (mListener != null) {
- mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo),
+ mListenerExecutor);
}
}
@@ -1572,7 +1635,8 @@
@Override
public void callSessionDtmfReceived(char dtmf) {
if (mListener != null) {
- mListener.callSessionDtmfReceived(dtmf);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived(
+ dtmf), mListenerExecutor);
}
}
@@ -1582,7 +1646,8 @@
@Override
public void callQualityChanged(CallQuality callQuality) {
if (mListener != null) {
- mListener.callQualityChanged(callQuality);
+ TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged(
+ callQuality), mListenerExecutor);
}
}
@@ -1594,8 +1659,9 @@
public void callSessionRtpHeaderExtensionsReceived(
@NonNull List<RtpHeaderExtension> extensions) {
if (mListener != null) {
- mListener.callSessionRtpHeaderExtensionsReceived(
- new ArraySet<RtpHeaderExtension>(extensions));
+ TelephonyUtils.runWithCleanCallingIdentity(()->
+ mListener.callSessionRtpHeaderExtensionsReceived(
+ new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor);
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 3e89cab..5d0d718 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -91,16 +91,14 @@
}
/**
- * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation
- * and becomes visible at the end
+ * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
+ * and remains visible at the end
*/
- @FlakyTest
+ @Postsubmit
@Test
fun navBarLayerVisibilityChanges() {
testSpec.assertLayers {
- this.isVisible(FlickerComponentName.NAV_BAR)
- .then()
- .isInvisible(FlickerComponentName.NAV_BAR)
+ this.isInvisible(FlickerComponentName.NAV_BAR)
.then()
.isVisible(FlickerComponentName.NAV_BAR)
}
@@ -153,16 +151,14 @@
}
/**
- * Checks that the nav bar starts the transition visible, then becomes invisible during
- * then unlocking animation and becomes visible at the end of the transition
+ * Checks that the nav bar starts the transition invisible, then becomes visible during
+ * the unlocking animation and remains visible at the end of the transition
*/
- @FlakyTest
+ @Postsubmit
@Test
fun navBarWindowsVisibilityChanges() {
testSpec.assertWm {
- this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
- .then()
- .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR)
+ this.isNonAppWindowInvisible(FlickerComponentName.NAV_BAR)
.then()
.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 6e65350..de9bbb6 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -19,6 +19,7 @@
"androidx.test.ext.junit",
"androidx.test.rules",
"services.core.unboosted",
+ "testables",
"truth-prebuilt",
"ub-uiautomator",
],
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 3eeba7d..1d65cc3 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -19,7 +19,11 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.filters.MediumTest
+import android.app.ActivityManager
+import android.app.ApplicationExitInfo
import android.graphics.Rect
+import android.os.Build
+import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.SystemClock
import android.provider.Settings
import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
@@ -27,10 +31,13 @@
import android.support.test.uiautomator.UiDevice
import android.support.test.uiautomator.UiObject2
import android.support.test.uiautomator.Until
+import android.testing.PollingCheck
import android.view.InputDevice
import android.view.MotionEvent
import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
@@ -51,22 +58,28 @@
class AnrTest {
companion object {
private const val TAG = "AnrTest"
+ private const val ALL_PIDS = 0
+ private const val NO_MAX = 0
}
- val mInstrumentation = InstrumentationRegistry.getInstrumentation()
- var mHideErrorDialogs = 0
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private var hideErrorDialogs = 0
+ private lateinit var PACKAGE_NAME: String
+ private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+ Build.HW_TIMEOUT_MULTIPLIER)
@Before
fun setUp() {
- val contentResolver = mInstrumentation.targetContext.contentResolver
- mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
+ val contentResolver = instrumentation.targetContext.contentResolver
+ hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
+ PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName()
}
@After
fun tearDown() {
- val contentResolver = mInstrumentation.targetContext.contentResolver
- Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs)
+ val contentResolver = instrumentation.targetContext.contentResolver
+ Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs)
}
@Test
@@ -86,19 +99,28 @@
private fun clickCloseAppOnAnrDialog() {
// Find anr dialog and kill app
- val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+ val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val closeAppButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
if (closeAppButton == null) {
fail("Could not find anr dialog")
return
}
+ val initialReasons = getExitReasons()
closeAppButton.click()
+ /**
+ * We must wait for the app to be fully closed before exiting this test. This is because
+ * another test may again invoke 'am start' for the same activity.
+ * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
+ * the killing logic will apply to the newly launched 'am start' instance, and the second
+ * test will fail because the unresponsive activity will never be launched.
+ */
+ waitForNewExitReason(initialReasons[0].timestamp)
}
private fun clickWaitOnAnrDialog() {
// Find anr dialog and tap on wait
- val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+ val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val waitButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
if (waitButton == null) {
@@ -108,9 +130,27 @@
waitButton.click()
}
+ private fun getExitReasons(): List<ApplicationExitInfo> {
+ lateinit var infos: List<ApplicationExitInfo>
+ instrumentation.runOnMainSync {
+ val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)
+ infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
+ }
+ return infos
+ }
+
+ private fun waitForNewExitReason(previousExitTimestamp: Long) {
+ PollingCheck.waitFor {
+ getExitReasons()[0].timestamp > previousExitTimestamp
+ }
+ val reasons = getExitReasons()
+ assertTrue(reasons[0].timestamp > previousExitTimestamp)
+ assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
+ }
+
private fun triggerAnr() {
startUnresponsiveActivity()
- val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+ val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val obj: UiObject2? = uiDevice.wait(Until.findObject(
By.text("Unresponsive gesture monitor")), 10000)
@@ -125,15 +165,14 @@
MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
- mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
+ instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
- // Todo: replace using timeout from android.hardware.input.IInputManager
- SystemClock.sleep(5000) // default ANR timeout for gesture monitors
+ SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
private fun startUnresponsiveActivity() {
val flags = " -W -n "
- val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity"
- mInstrumentation.uiAutomation.executeShellCommand(startCmd)
+ val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
+ instrumentation.uiAutomation.executeShellCommand(startCmd)
}
-}
\ No newline at end of file
+}
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index da5468e..b21d7b5 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -44,6 +44,7 @@
import com.android.cts.install.lib.Install;
import com.android.cts.install.lib.InstallUtils;
import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
import org.junit.After;
import org.junit.Before;
@@ -108,6 +109,7 @@
@Test
public void cleanUp() throws Exception {
Files.deleteIfExists(mTestStateFile.toPath());
+ Uninstall.packages(TestApp.A, TestApp.B);
}
@Test
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
new file mode 100644
index 0000000..f7d36970
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class VcnCellUnderlyingNetworkPriorityTest {
+ private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>();
+ private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
+
+ // Package private for use in VcnGatewayConnectionConfigTest
+ static VcnCellUnderlyingNetworkPriority getTestNetworkPriority() {
+ return new VcnCellUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .setAllowedPlmnIds(ALLOWED_PLMN_IDS)
+ .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
+ .setAllowRoaming(true /* allowRoaming */)
+ .setRequireOpportunistic(true /* requireOpportunistic */)
+ .build();
+ }
+
+ @Test
+ public void testBuilderAndGetters() {
+ final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+ assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
+ assertTrue(networkPriority.allowMetered());
+ assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedPlmnIds());
+ assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
+ assertTrue(networkPriority.allowRoaming());
+ assertTrue(networkPriority.requireOpportunistic());
+ }
+
+ @Test
+ public void testBuilderAndGettersForDefaultValues() {
+ final VcnCellUnderlyingNetworkPriority networkPriority =
+ new VcnCellUnderlyingNetworkPriority.Builder().build();
+ assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
+ assertFalse(networkPriority.allowMetered());
+ assertEquals(new HashSet<String>(), networkPriority.getAllowedPlmnIds());
+ assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
+ assertFalse(networkPriority.allowRoaming());
+ assertFalse(networkPriority.requireOpportunistic());
+ }
+
+ @Test
+ public void testPersistableBundle() {
+ final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+ assertEquals(
+ networkPriority,
+ VcnUnderlyingNetworkPriority.fromPersistableBundle(
+ networkPriority.toPersistableBundle()));
+ }
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index dc338ae..724c33f 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -38,6 +38,7 @@
import org.junit.runner.RunWith;
import java.util.Arrays;
+import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -50,9 +51,17 @@
};
public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
+ private static final LinkedHashSet<VcnUnderlyingNetworkPriority> UNDERLYING_NETWORK_PRIORITIES =
+ new LinkedHashSet();
+
static {
Arrays.sort(EXPOSED_CAPS);
Arrays.sort(UNDERLYING_CAPS);
+
+ UNDERLYING_NETWORK_PRIORITIES.add(
+ VcnCellUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+ UNDERLYING_NETWORK_PRIORITIES.add(
+ VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority());
}
public static final long[] RETRY_INTERVALS_MS =
@@ -82,7 +91,10 @@
// Public for use in VcnGatewayConnectionTest
public static VcnGatewayConnectionConfig buildTestConfig() {
- return buildTestConfigWithExposedCaps(EXPOSED_CAPS);
+ final VcnGatewayConnectionConfig.Builder builder =
+ newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+
+ return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS);
}
private static VcnGatewayConnectionConfig.Builder newBuilder() {
@@ -159,6 +171,15 @@
}
@Test
+ public void testBuilderRequiresNonNullNetworkPriorities() {
+ try {
+ newBuilder().setVcnUnderlyingNetworkPriorities(null);
+ fail("Expected exception due to invalid underlyingNetworkPriorities");
+ } catch (NullPointerException e) {
+ }
+ }
+
+ @Test
public void testBuilderRequiresNonNullRetryInterval() {
try {
newBuilder().setRetryIntervalsMillis(null);
@@ -195,6 +216,7 @@
Arrays.sort(exposedCaps);
assertArrayEquals(EXPOSED_CAPS, exposedCaps);
+ assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams());
assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java
new file mode 100644
index 0000000..dd272cb
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class VcnWifiUnderlyingNetworkPriorityTest {
+ private static final String SSID = "TestWifi";
+ private static final int INVALID_NETWORK_QUALITY = -1;
+
+ // Package private for use in VcnGatewayConnectionConfigTest
+ static VcnWifiUnderlyingNetworkPriority getTestNetworkPriority() {
+ return new VcnWifiUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .setSsid(SSID)
+ .build();
+ }
+
+ @Test
+ public void testBuilderAndGetters() {
+ final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+ assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
+ assertTrue(networkPriority.allowMetered());
+ assertEquals(SSID, networkPriority.getSsid());
+ }
+
+ @Test
+ public void testBuilderAndGettersForDefaultValues() {
+ final VcnWifiUnderlyingNetworkPriority networkPriority =
+ new VcnWifiUnderlyingNetworkPriority.Builder().build();
+ assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
+ assertFalse(networkPriority.allowMetered());
+ assertNull(SSID, networkPriority.getSsid());
+ }
+
+ @Test
+ public void testBuildWithInvalidNetworkQuality() {
+ try {
+ new VcnWifiUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(INVALID_NETWORK_QUALITY);
+ fail("Expected to fail due to the invalid network quality");
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
+ public void testPersistableBundle() {
+ final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+ assertEquals(
+ networkPriority,
+ VcnUnderlyingNetworkPriority.fromPersistableBundle(
+ networkPriority.toPersistableBundle()));
+ }
+}