Merge "Adding feature flag and replace existing boolean with settings check" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7317ecd..a80194c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -335,6 +335,17 @@
mode: "exported",
}
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc",
+ aconfig_declarations: "android.os.flags-aconfig",
+}
+
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc-test",
+ aconfig_declarations: "android.os.flags-aconfig",
+ mode: "test",
+}
+
// VirtualDeviceManager
cc_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-cc",
diff --git a/core/api/current.txt b/core/api/current.txt
index 62980ed..42ac6b7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6085,7 +6085,7 @@
public class GrammaticalInflectionManager {
method public int getApplicationGrammaticalGender();
- method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
+ method @FlaggedApi("android.app.system_terms_of_address_enabled") @RequiresPermission("android.permission.READ_SYSTEM_GRAMMATICAL_GENDER") public int getSystemGrammaticalGender();
method public void setRequestedApplicationGrammaticalGender(int);
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9c1a8e8..0ab2588 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -652,6 +652,7 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.webkit.WebViewProviderResponse> CREATOR;
field public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
+ field public static final int STATUS_FAILED_OTHER = 11; // 0xb
field public static final int STATUS_FAILED_WAITING_FOR_RELRO = 3; // 0x3
field public static final int STATUS_SUCCESS = 0; // 0x0
field @Nullable public final android.content.pm.PackageInfo packageInfo;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index afb796b..d190c62 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4386,7 +4386,7 @@
field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
- field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+ field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -4821,7 +4821,7 @@
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
- method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist();
+ method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.List<java.lang.String> getCameraPrivacyAllowlist();
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int);
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
@@ -4844,6 +4844,12 @@
method public boolean isEnabled();
}
+ @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static class SensorPrivacyManager.StateTypes {
+ field public static final int DISABLED = 2; // 0x2
+ field public static final int ENABLED = 1; // 0x1
+ field public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS = 3; // 0x3
+ }
+
}
package android.hardware.biometrics {
@@ -12967,7 +12973,7 @@
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
+ public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
method public int getErrorCode();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6fb6b0d..53d0c03 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1519,6 +1519,7 @@
package android.hardware {
public final class SensorPrivacyManager {
+ method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int);
}
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 483a6e1..4ce983f 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,8 +16,10 @@
package android.app;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.res.Configuration;
@@ -127,6 +129,7 @@
*
* @see Configuration#getGrammaticalGender
*/
+ @RequiresPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
@FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
@Configuration.GrammaticalGender
public int getSystemGrammaticalGender() {
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index 4f70604..cb5e986 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -32,7 +32,7 @@
public BundlePolicyValue(Bundle value) {
super(value);
if (Flags.devicePolicySizeTrackingInternalEnabled()) {
- PolicySizeVerifier.enforceMaxParcelableFieldsLength(value);
+ PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
}
}
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index 63c3a4cb..7526a7b 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -24,7 +24,6 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Parcel;
@@ -60,9 +59,6 @@
@TestApi
public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
super(identifier);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
- PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter);
- }
mFilter = Objects.requireNonNull(filter);
}
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 792ebc6..7f8e50e 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -17,12 +17,12 @@
package android.app.admin;
import android.content.ComponentName;
+import android.os.Bundle;
import android.os.Parcelable;
import android.os.PersistableBundle;
import com.android.internal.util.Preconditions;
-import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -71,44 +71,51 @@
for (String key : current.keySet()) {
enforceMaxStringLength(key, "key in " + argName);
Object value = current.get(key);
- if (value instanceof String) {
- enforceMaxStringLength((String) value, "string value in " + argName);
- } else if (value instanceof String[]) {
- for (String str : (String[]) value) {
+ if (value instanceof String str) {
+ enforceMaxStringLength(str, "string value in " + argName);
+ } else if (value instanceof String[] strArray) {
+ for (String str : strArray) {
enforceMaxStringLength(str, "string value in " + argName);
}
- } else if (value instanceof PersistableBundle) {
- queue.add((PersistableBundle) value);
+ } else if (value instanceof PersistableBundle persistableBundle) {
+ queue.add(persistableBundle);
}
}
}
}
/**
- * Throw if Parcelable contains any string that's too long to be serialized.
+ * Throw if bundle contains any string that's too long to be serialized. This follows the
+ * serialization logic in BundlePolicySerializer#writeBundle.
*/
- public static void enforceMaxParcelableFieldsLength(Parcelable parcelable) {
- // TODO(b/326662716) rework to protect against infinite recursion.
- if (true) {
- return;
- }
- Class<?> clazz = parcelable.getClass();
-
- Field[] fields = clazz.getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- try {
- Object value = field.get(parcelable);
- if (value instanceof String) {
- String stringValue = (String) value;
- enforceMaxStringLength(stringValue, field.getName());
+ public static void enforceMaxBundleFieldsLength(Bundle bundle) {
+ Queue<Bundle> queue = new ArrayDeque<>();
+ queue.add(bundle);
+ while (!queue.isEmpty()) {
+ Bundle current = queue.remove();
+ for (String key : current.keySet()) {
+ enforceMaxStringLength(key, "key in Bundle");
+ Object value = current.get(key);
+ if (value instanceof String str) {
+ enforceMaxStringLength(str, "string value in Bundle with "
+ + "key" + key);
+ } else if (value instanceof String[] strArray) {
+ for (String str : strArray) {
+ enforceMaxStringLength(str, "string value in Bundle with"
+ + " key" + key);
+ }
+ } else if (value instanceof Bundle b) {
+ queue.add(b);
}
-
- if (value instanceof Parcelable) {
- enforceMaxParcelableFieldsLength((Parcelable) value);
+ else if (value instanceof Parcelable[] parcelableArray) {
+ for (Parcelable parcelable : parcelableArray) {
+ if (!(parcelable instanceof Bundle)) {
+ throw new IllegalArgumentException("bundle-array can only hold "
+ + "Bundles");
+ }
+ queue.add((Bundle) parcelable);
+ }
}
- } catch (IllegalAccessException e) {
- e.printStackTrace();
}
}
}
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index cdda12e..3f941da 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,6 +273,10 @@
* to the <code>retailDemo</code> value of
* {@link android.R.attr#protectionLevel}.
*
+ * @deprecated This flag has been replaced by the
+ * {@link android.R.string#config_defaultRetailDemo retail demo role} and is a
+ * no-op since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+ *
* @hide
*/
@SystemApi
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
deleted file mode 100644
index 838e41e..0000000
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) 2024, 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;
-
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
-}
-
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 851ce2a..19d1029 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -16,7 +16,6 @@
package android.hardware;
-import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
/** @hide */
@@ -48,7 +47,7 @@
void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
- List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+ List<String> getCameraPrivacyAllowlist();
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
int getToggleSensorPrivacyState(int toggleType, int sensor);
@@ -62,6 +61,10 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
boolean isCameraPrivacyEnabled(String packageName);
+ /** @hide */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+ void setCameraPrivacyAllowlist(in List<String> allowlist);
+
// =============== End of transactions used on native side as well ============================
void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 6294a8d..08b9064 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -43,7 +43,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -204,6 +204,8 @@
* Types of state which can exist for the sensor privacy toggle
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
public static class StateTypes {
private StateTypes() {}
@@ -217,30 +219,12 @@
*/
public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
- /**
- * Constant indicating privacy is enabled except for the automotive driver assistance apps
- * which are helpful for driving.
- */
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS;
-
/**
* Constant indicating privacy is enabled except for the automotive driver assistance apps
* which are required by car manufacturer for driving.
*/
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS;
-
- /**
- * Constant indicating privacy is enabled except for the automotive driver assistance apps
- * which are both helpful for driving and also apps required by car manufacturer for
- * driving.
- */
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS;
+ public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS =
+ SensorPrivacyIndividualEnabledSensorProto.ENABLED_EXCEPT_ALLOWLISTED_APPS;
/**
* Types of state which can exist for a sensor privacy toggle
@@ -250,9 +234,7 @@
@IntDef(value = {
ENABLED,
DISABLED,
- AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS,
- AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS,
- AUTOMOTIVE_DRIVER_ASSISTANCE_APPS
+ ENABLED_EXCEPT_ALLOWLISTED_APPS
})
@Retention(RetentionPolicy.SOURCE)
public @interface StateType {}
@@ -369,9 +351,6 @@
private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
- @GuardedBy("mLock")
- private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null;
-
/** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
* listeners */
@NonNull
@@ -397,7 +376,8 @@
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) {
+ public void onSensorPrivacyStateChanged(@ToggleType int toggleType,
+ @Sensors.Sensor int sensor, @StateTypes.StateType int state) {
synchronized (mLock) {
for (int i = 0; i < mToggleListeners.size(); i++) {
OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
@@ -725,6 +705,8 @@
/**
* Returns sensor privacy state for a specific sensor.
*
+ * @param toggleType The type of toggle to use
+ * @param sensor The sensor to check
* @return int sensor privacy state.
*
* @hide
@@ -741,9 +723,10 @@
}
}
- /**
+ /**
* Returns if camera privacy is enabled for a specific package.
*
+ * @param packageName The package to check
* @return boolean sensor privacy state.
*
* @hide
@@ -763,29 +746,41 @@
* Returns camera privacy allowlist.
*
* @return List of automotive driver assistance packages for
- * privacy allowlisting. The returned map includes the package
- * name as key and the value is a Boolean which tells if that package
- * is required by the car manufacturer as mandatory package for driving.
+ * privacy allowlisting.
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() {
+ public @NonNull List<String> getCameraPrivacyAllowlist() {
synchronized (mLock) {
- if (mCameraPrivacyAllowlist == null) {
- mCameraPrivacyAllowlist = new ArrayMap<>();
- try {
- for (CameraPrivacyAllowlistEntry entry :
- mService.getCameraPrivacyAllowlist()) {
- mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ return mService.getCameraPrivacyAllowlist();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mCameraPrivacyAllowlist;
+ }
+ }
+
+ /**
+ * Sets camera privacy allowlist.
+ *
+ * @param allowlist List of automotive driver assistance packages for
+ * privacy allowlisting.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ public void setCameraPrivacyAllowlist(@NonNull List<String> allowlist) {
+ synchronized (mLock) {
+ try {
+ mService.setCameraPrivacyAllowlist(allowlist);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -867,6 +862,7 @@
/**
* Sets sensor privacy to the specified state for an individual sensor.
*
+ * @param source the source using which the sensor is toggled
* @param sensor the sensor which to change the state for
* @param state the state to which sensor privacy should be set.
*
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 68e8c72..67a3978 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1733,6 +1733,8 @@
private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
boolean keepHistory);
@CriticalNative
+ private static native long nativeSplit(long destNativePtr, long sourceNativePtr, int idBits);
+ @CriticalNative
private static native int nativeGetId(long nativePtr);
@CriticalNative
private static native int nativeGetDeviceId(long nativePtr);
@@ -3767,86 +3769,23 @@
}
/**
- * Splits a motion event such that it includes only a subset of pointer ids.
+ * Splits a motion event such that it includes only a subset of pointer IDs.
+ * @param idBits the bitset indicating all of the pointer IDs from this motion event that should
+ * be in the new split event. idBits must be a non-empty subset of the pointer IDs
+ * contained in this event.
* @hide
*/
+ // TODO(b/327503168): Pass downTime as a parameter to split.
@UnsupportedAppUsage
+ @NonNull
public final MotionEvent split(int idBits) {
- MotionEvent ev = obtain();
- synchronized (gSharedTempLock) {
- final int oldPointerCount = nativeGetPointerCount(mNativePtr);
- ensureSharedTempPointerCapacity(oldPointerCount);
- final PointerProperties[] pp = gSharedTempPointerProperties;
- final PointerCoords[] pc = gSharedTempPointerCoords;
- final int[] map = gSharedTempPointerIndexMap;
-
- final int oldAction = nativeGetAction(mNativePtr);
- final int oldActionMasked = oldAction & ACTION_MASK;
- final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
- >> ACTION_POINTER_INDEX_SHIFT;
- int newActionPointerIndex = -1;
- int newPointerCount = 0;
- for (int i = 0; i < oldPointerCount; i++) {
- nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
- final int idBit = 1 << pp[newPointerCount].id;
- if ((idBit & idBits) != 0) {
- if (i == oldActionPointerIndex) {
- newActionPointerIndex = newPointerCount;
- }
- map[newPointerCount] = i;
- newPointerCount += 1;
- }
- }
-
- if (newPointerCount == 0) {
- throw new IllegalArgumentException("idBits did not match any ids in the event");
- }
-
- final int newAction;
- if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
- if (newActionPointerIndex < 0) {
- // An unrelated pointer changed.
- newAction = ACTION_MOVE;
- } else if (newPointerCount == 1) {
- // The first/last pointer went down/up.
- newAction = oldActionMasked == ACTION_POINTER_DOWN
- ? ACTION_DOWN
- : (getFlags() & FLAG_CANCELED) == 0 ? ACTION_UP : ACTION_CANCEL;
- } else {
- // A secondary pointer went down/up.
- newAction = oldActionMasked
- | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
- }
- } else {
- // Simple up/down/cancel/move or other motion action.
- newAction = oldAction;
- }
-
- final int historySize = nativeGetHistorySize(mNativePtr);
- for (int h = 0; h <= historySize; h++) {
- final int historyPos = h == historySize ? HISTORY_CURRENT : h;
-
- for (int i = 0; i < newPointerCount; i++) {
- nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
- }
-
- final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
- if (h == 0) {
- ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
- nativeGetDisplayId(mNativePtr),
- newAction, nativeGetFlags(mNativePtr),
- nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
- nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
- nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
- nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
- newPointerCount, pp, pc);
- } else {
- nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
- }
- }
- return ev;
+ if (idBits == 0) {
+ throw new IllegalArgumentException(
+ "idBits must contain at least one pointer from this motion event");
}
+ MotionEvent event = obtain();
+ event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
+ return event;
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 828004b..d29963c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25,6 +25,7 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -5629,7 +5630,7 @@
@Nullable
private ViewTranslationCallback mViewTranslationCallback;
- private float mFrameContentVelocity = 0;
+ private float mFrameContentVelocity = -1;
@Nullable
@@ -5660,6 +5661,9 @@
protected long mMinusTwoFrameIntervalMillis = 0;
private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ private float mLastFrameX = Float.NaN;
+ private float mLastFrameY = Float.NaN;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -24597,7 +24601,10 @@
public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
- mFrameContentVelocity = 0;
+
+ mFrameContentVelocity = -1;
+ mLastFrameX = mLeft + mRenderNode.getTranslationX();
+ mLastFrameY = mTop + mRenderNode.getTranslationY();
/*
* Draw traversal performs several drawing steps which must be executed
@@ -33673,6 +33680,17 @@
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
viewRootImpl.recordViewPercentage(sizePercentage);
}
+ if (viewVelocityApi()) {
+ float velocity = mFrameContentVelocity;
+ if (velocity < 0f) {
+ velocity = calculateVelocity();
+ }
+ if (velocity > 0f) {
+ float frameRate = convertVelocityToFrameRate(velocity);
+ viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
+ return;
+ }
+ }
if (!Float.isNaN(mPreferredFrameRate)) {
if (mPreferredFrameRate < 0) {
if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
@@ -33695,6 +33713,23 @@
}
}
+ private float convertVelocityToFrameRate(float velocityPps) {
+ float density = getResources().getDisplayMetrics().density;
+ float velocityDps = velocityPps / density;
+ // Choose a frame rate in increments of 10fps
+ return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
+ }
+
+ private float calculateVelocity() {
+ // This current calculation is very simple. If something on the screen moved, then
+ // it votes for the highest velocity. If it doesn't move, then return 0.
+ float x = mLeft + mRenderNode.getTranslationX();
+ float y = mTop + mRenderNode.getTranslationY();
+
+ return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
+ ? 100_000f : 0f;
+ }
+
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a7cb169..42f6405 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -31,6 +31,7 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -3447,7 +3448,10 @@
// other windows to resize/move based on the raw frame of the window, waiting until we
// can finish laying out this window and get back to the window manager with the
// ultimately computed insets.
- insetsPending = computesInternalInsets;
+ insetsPending = computesInternalInsets
+ // If this window provides insets via params, its insets source frame can be
+ // updated directly without waiting for WindowSession#setInsets.
+ && mWindowAttributes.providedInsets == null;
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
@@ -7568,7 +7572,8 @@
}
// For the variable refresh rate project
- if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+ if (handled && shouldTouchBoost(action & MotionEvent.ACTION_MASK,
+ mWindowAttributes.type)) {
// set the frame rate to the maximum value.
mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -12395,6 +12400,17 @@
mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
+ if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
+ // We've received a velocity, so we'll let the velocity control the
+ // frame rate unless we receive additional motion events.
+ mIsTouchBoosting = false;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(
+ Trace.TRACE_TAG_VIEW,
+ "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
+ );
+ }
+ }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
} finally {
@@ -12420,9 +12436,8 @@
}
private boolean shouldTouchBoost(int motionEventAction, int windowType) {
- boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
- || motionEventAction == MotionEvent.ACTION_MOVE
- || motionEventAction == MotionEvent.ACTION_UP;
+ // boost for almost all input
+ boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE;
boolean undesiredType = windowType == TYPE_INPUT_METHOD
&& sToolkitFrameRateTypingReadOnlyFlagValue;
// use toolkitSetFrameRate flag to gate the change
@@ -12528,6 +12543,14 @@
}
/**
+ * Returns whether touch boost is currently enabled.
+ */
+ @VisibleForTesting
+ public boolean getIsTouchBoosting() {
+ return mIsTouchBoosting;
+ }
+
+ /**
* Get the value of mFrameRateCompatibility
*/
@VisibleForTesting
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 64e5a5b..cf6b9e5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1160,12 +1160,10 @@
// denylist only applies to not important views
if (!view.isImportantForAutofill() && isActivityDeniedForAutofill()) {
- Log.d(TAG, "view is not autofillable - activity denied for autofill");
return false;
}
if (isActivityAllowedForAutofill()) {
- Log.d(TAG, "view is autofillable - activity allowed for autofill");
return true;
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3be76cc..985f542 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1153,7 +1153,6 @@
}
final boolean startInput;
synchronized (mH) {
- mImeDispatcher.clear();
if (getBindSequenceLocked() != sequence) {
return;
}
@@ -2909,8 +2908,6 @@
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
* @param executor The executor to run the callback on.
* @param callback {@code true>} would be received if delegation was accepted.
- * @return {@code true} if view belongs to allowed delegate package declared in {@link
- * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 01fdd1d..8f1b72e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -94,6 +94,9 @@
// error for namespace lookup
public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
+ // generic error for future use
+ static final int LIBLOAD_FAILED_OTHER = 11;
+
/**
* Stores the timestamps at which various WebView startup events occurred in this process.
*/
diff --git a/core/java/android/webkit/WebViewProviderResponse.java b/core/java/android/webkit/WebViewProviderResponse.java
index 84e34a3..926aaff 100644
--- a/core/java/android/webkit/WebViewProviderResponse.java
+++ b/core/java/android/webkit/WebViewProviderResponse.java
@@ -40,6 +40,7 @@
STATUS_SUCCESS,
STATUS_FAILED_WAITING_FOR_RELRO,
STATUS_FAILED_LISTING_WEBVIEW_PACKAGES,
+ STATUS_FAILED_OTHER,
})
@Retention(RetentionPolicy.SOURCE)
private @interface WebViewProviderStatus {}
@@ -49,6 +50,7 @@
WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES =
WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ public static final int STATUS_FAILED_OTHER = WebViewFactory.LIBLOAD_FAILED_OTHER;
public WebViewProviderResponse(
@Nullable PackageInfo packageInfo, @WebViewProviderStatus int status) {
diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java
index 8ada598..07576a2 100644
--- a/core/java/android/webkit/WebViewUpdateManager.java
+++ b/core/java/android/webkit/WebViewUpdateManager.java
@@ -127,7 +127,7 @@
*
* This choice will be stored persistently.
*
- * @param newProvider the package name to use, or null to reset to default.
+ * @param newProvider the package name to use.
* @return the package name which is now in use, which may not be the
* requested one if it was not usable.
*/
@@ -155,7 +155,7 @@
/**
* Get the WebView provider which will be used if no explicit choice has been made.
*
- * The default provider is not guaranteed to be currently valid/usable.
+ * The default provider is not guaranteed to be a valid/usable WebView implementation.
*
* @return the default WebView provider.
*/
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 55b2251..0b99df3 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1488,6 +1490,11 @@
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
}
}
@@ -1810,6 +1817,11 @@
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
maxScroll, 0, 0, width / 2, 0);
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
final boolean movingRight = velocityX > 0;
View currentFocused = findFocus();
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index a1ebde7..42c2d80 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -726,6 +728,12 @@
* isFinished() is correct.
*/
mScroller.computeScrollOffset();
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
|| !mEdgeGlowTop.isFinished();
// Catch the edge effect if it is active.
@@ -1573,6 +1581,11 @@
// Keep on drawing until the animation has finished.
postInvalidateOnAnimation();
}
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
} else {
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
@@ -1884,6 +1897,10 @@
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
if (mFlingStrictSpan == null) {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index b7cb392..7c9fda5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -92,7 +92,7 @@
mIndex = 0;
mStartingIndex = 0;
mSize = 0;
- if (expectedSize > mMaxSize) {
+ if (expectedSize >= mMaxSize) {
resize(expectedSize);
}
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b39d5cf..23adb8f7 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -622,6 +622,18 @@
return reinterpret_cast<jlong>(destEvent);
}
+static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
+ jint idBits) {
+ MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
+ if (!destEvent) {
+ destEvent = new MotionEvent();
+ }
+ MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr);
+ destEvent->splitFrom(*sourceEvent, static_cast<std::bitset<MAX_POINTER_ID + 1>>(idBits),
+ InputEvent::nextId());
+ return reinterpret_cast<jlong>(destEvent);
+}
+
static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
@@ -837,6 +849,7 @@
// --------------- @CriticalNative ------------------
{"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy},
+ {"nativeSplit", "(JJI)J", (void*)android_view_MotionEvent_nativeSplit},
{"nativeGetId", "(J)I", (void*)android_view_MotionEvent_nativeGetId},
{"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId},
{"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource},
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index e368c6a..53aa710 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,9 +91,7 @@
enum StateType {
ENABLED = 1;
DISABLED = 2;
- AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3;
- AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4;
- AUTO_DRIVER_ASSISTANCE_APPS = 5;
+ ENABLED_EXCEPT_ALLOWLISTED_APPS = 3;
}
// DEPRECATED
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ba9751f..52bad21 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8679,7 +8679,7 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <service android:name="com.android.server.companion.InactiveAssociationsRemovalService"
+ <service android:name="com.android.server.companion.association.InactiveAssociationsRemovalService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c882938..d89f236 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,7 +301,9 @@
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
<!-- Additional flag from base permission type: this permission will be granted to the
- retail demo app, as defined by the OEM. -->
+ retail demo app, as defined by the OEM.
+ This flag has been replaced by the retail demo role and is a no-op since Android V.
+ -->
<flag name="retailDemo" value="0x1000000" />
<!-- Additional flag from base permission type: this permission will be granted to the
recents app. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 90f2731..4f20fce 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3551,10 +3551,6 @@
<string name="config_keyguardComponent" translatable="false"
>com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
- <!-- Screen record dialog component -->
- <string name="config_screenRecorderComponent" translatable="false"
- >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
-
<!-- The component name of a special dock app that merely launches a dream.
We don't want to launch this app when docked because it causes an unnecessary
activity transition. We just want to start the dream. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a025c8d..150951f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -375,7 +375,6 @@
<java-symbol type="string" name="config_recentsComponentName" />
<java-symbol type="string" name="config_systemUIServiceComponent" />
<java-symbol type="string" name="config_controlsPackage" />
- <java-symbol type="string" name="config_screenRecorderComponent" />
<java-symbol type="string" name="config_somnambulatorComponent" />
<java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
<java-symbol type="string" name="config_screenshotServiceComponent" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 61e6a36..7d740ef 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -88,6 +88,9 @@
<!-- Colombia: 1-6 digits (not confirmed) -->
<shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
+ <!-- Costa Rica -->
+ <shortcode country="cr" pattern="\\d{1,6}" free="466453" />
+
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -143,6 +146,9 @@
<!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
<shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
+ <!-- Guatemala -->
+ <shortcode country="gt" pattern="\\d{1,6}" free="466453" />
+
<!-- Croatia -->
<shortcode country="hr" pattern="\\d{1,5}" free="13062" />
@@ -241,10 +247,10 @@
<shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
<!-- Pakistan -->
- <shortcode country="pk" pattern="\\d{1,5}" free="2057" />
+ <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" />
<!-- Palestine: 5 digits, known premium codes listed -->
- <shortcode country="ps" pattern="\\d{1,5}" free="37477" />
+ <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
@@ -324,4 +330,7 @@
<!-- South Africa -->
<shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" />
+ <!-- Zimbabwe -->
+ <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
+
</shortcodes>
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
index 866e1a9..5029212 100644
--- a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -14,105 +14,150 @@
~ limitations under the License
-->
-<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/horizontal_scroll_view">
+ android:orientation="vertical">
- <LinearLayout
+ <HorizontalScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:layout_height="match_parent"
+ android:id="@+id/horizontal_scroll_view">
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- </LinearLayout>
-</HorizontalScrollView>
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <view
+ class="android.widget.HorizontalScrollViewFunctionalTest$MyHorizontalScrollView"
+ android:id="@+id/my_horizontal_scroll_view"
+ android:layout_width="90dp"
+ android:layout_height="90dp"
+ android:background="#FFF"
+ android:defaultFocusHighlightEnabled="false">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:background="#00F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0FF"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0F0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#FF0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F00"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F0F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ </LinearLayout>
+ </view>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
index 61fabf8..db8cd02 100644
--- a/core/tests/coretests/res/layout/activity_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -14,105 +14,150 @@
~ limitations under the License
-->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/scroll_view">
+ android:orientation="vertical">
- <LinearLayout
+ <ScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="match_parent"
+ android:id="@+id/scroll_view">
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- </LinearLayout>
-</ScrollView>
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+ </ScrollView>
+
+ <view
+ class="android.widget.ScrollViewFunctionalTest$MyScrollView"
+ android:id="@+id/my_scroll_view"
+ android:layout_width="90dp"
+ android:layout_height="90dp"
+ android:background="#FFF"
+ android:defaultFocusHighlightEnabled="false">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:background="#00F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0FF"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0F0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#FF0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F00"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F0F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ </LinearLayout>
+ </view>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/view_velocity_test.xml b/core/tests/coretests/res/layout/view_velocity_test.xml
new file mode 100644
index 0000000..98154a4
--- /dev/null
+++ b/core/tests/coretests/res/layout/view_velocity_test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frameLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:id="@+id/moving_view"
+ android:layout_width="50dp"
+ android:layout_height="50dp" />
+</FrameLayout>
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
new file mode 100644
index 0000000..128c54b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 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.view;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.SystemClock;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewVelocityTest {
+
+ @Rule
+ public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+ ViewCaptureTestActivity.class);
+
+ private Activity mActivity;
+ private View mMovingView;
+ private ViewRootImpl mViewRoot;
+
+ @Before
+ public void setUp() throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ mActivityRule.runOnUiThread(() -> {
+ mActivity.setContentView(R.layout.view_velocity_test);
+ mMovingView = mActivity.findViewById(R.id.moving_view);
+ });
+ ViewParent parent = mActivity.getWindow().getDecorView().getParent();
+ while (parent instanceof View) {
+ parent = parent.getParent();
+ }
+ mViewRoot = (ViewRootImpl) parent;
+ }
+
+ @UiThreadTest
+ @Test
+ public void frameRateChangesWhenContentMoves() {
+ mMovingView.offsetLeftAndRight(100);
+ float frameRate = mViewRoot.getPreferredFrameRate();
+ assertTrue(frameRate > 0);
+ }
+
+ @UiThreadTest
+ @Test
+ public void firstFrameNoMovement() {
+ assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
+ }
+
+ @Test
+ public void touchBoostDisable() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent down = MotionEvent.obtain(
+ /* downTime */ now,
+ /* eventTime */ now,
+ /* action */ MotionEvent.ACTION_DOWN,
+ /* x */ 0f,
+ /* y */ 0f,
+ /* metaState */ 0
+ );
+ mActivity.dispatchTouchEvent(down);
+ mMovingView.offsetLeftAndRight(10);
+ });
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ });
+
+ mActivityRule.runOnUiThread(() -> {
+ assertFalse(mViewRoot.getIsTouchBoosting());
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index df212eb..cd38bd6 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
package android.widget;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
import android.util.PollingCheck;
import androidx.test.filters.MediumTest;
@@ -43,14 +49,21 @@
public class HorizontalScrollViewFunctionalTest {
private HorizontalScrollViewActivity mActivity;
private HorizontalScrollView mHorizontalScrollView;
+ private MyHorizontalScrollView mMyHorizontalScrollView;
@Rule
public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
HorizontalScrollViewActivity.class);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+ mMyHorizontalScrollView =
+ (MyHorizontalScrollView) mActivity.findViewById(R.id.my_horizontal_scroll_view);
}
@Test
@@ -79,6 +92,22 @@
assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testSetVelocity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ mMyHorizontalScrollView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyHorizontalScrollView.fling(100);
+ });
+ // set setFrameContentVelocity should be called when fling.
+ assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, true);
+ }
+
static class WatchedEdgeEffect extends EdgeEffect {
public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
@@ -92,5 +121,29 @@
onAbsorbLatch.countDown();
}
}
+
+ public static class MyHorizontalScrollView extends ScrollView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyHorizontalScrollView(Context context) {
+ super(context);
+ }
+
+ public MyHorizontalScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
index 109c808..a60b2a13 100644
--- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
package android.widget;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
import android.util.PollingCheck;
import androidx.test.filters.MediumTest;
@@ -43,14 +49,20 @@
public class ScrollViewFunctionalTest {
private ScrollViewActivity mActivity;
private ScrollView mScrollView;
+ private MyScrollView mMyScrollView;
@Rule
public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
ScrollViewActivity.class);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mScrollView = mActivity.findViewById(R.id.scroll_view);
+ mMyScrollView = (MyScrollView) mActivity.findViewById(R.id.my_scroll_view);
}
@Test
@@ -79,6 +91,22 @@
assertEquals(maxScroll, mScrollView.getScrollY());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testSetVelocity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ mMyScrollView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyScrollView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyScrollView.fling(100);
+ });
+ // set setFrameContentVelocity should be called when fling.
+ assertEquals(mMyScrollView.isSetVelocityCalled, true);
+ }
+
static class WatchedEdgeEffect extends EdgeEffect {
public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
@@ -92,5 +120,29 @@
onAbsorbLatch.countDown();
}
}
+
+ public static class MyScrollView extends ScrollView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyScrollView(Context context) {
+ super(context);
+ }
+
+ public MyScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 8fd6ffe..474430e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -717,11 +717,6 @@
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
-
- if (mShouldReorderBubblesAfterGestureCompletes) {
- mShouldReorderBubblesAfterGestureCompletes = false;
- updateBubbleOrderInternal(mBubbleData.getBubbles(), true);
- }
}
};
@@ -2732,6 +2727,12 @@
ev.getAction() != MotionEvent.ACTION_UP
&& ev.getAction() != MotionEvent.ACTION_CANCEL;
+ // If there is a deferred reorder action, and the gesture is over, run it now.
+ if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = false;
+ updateBubbleOrderInternal(mBubbleData.getBubbles(), false);
+ }
+
return dispatched;
}
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 2ea4e3f..af169f4 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -540,7 +540,7 @@
}
bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
- return bitmap && width <= bitmap->width() && height <= bitmap->height();
+ return bitmap && width == bitmap->width() && height == bitmap->height();
}
void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index f8dd756..6223acf 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -24,7 +24,10 @@
import java.util.Objects;
/**
- * An instances of this class represents a session of media playback.
+ * An instance of this class represents a session of media playback used to report playback
+ * metrics and events.
+ *
+ * Create a new instance using {@link MediaMetricsManager#createPlaybackSession}.
*/
public final class PlaybackSession implements AutoCloseable {
private final @NonNull String mId;
@@ -80,6 +83,21 @@
mManager.reportTrackChangeEvent(mId, event);
}
+ /**
+ * A session ID is used to identify a unique playback and to tie together lower-level
+ * playback components.
+ *
+ * Associate this session with a {@link MediaCodec} by passing the ID into
+ * {@link MediaFormat} through {@link MediaFormat#LOG_SESSION_ID} when
+ * creating the {@link MediaCodec}.
+ *
+ * Associate this session with an {@link AudioTrack} by calling
+ * {@link AudioTrack#setLogSessionId}.
+ *
+ * Associate this session with {@link MediaDrm} and {@link MediaCrypto} by calling
+ * {@link MediaDrm#getPlaybackComponent} and then calling
+ * {@link PlaybackComponent#setLogSessionId}.
+ */
public @NonNull LogSessionId getSessionId() {
return mLogSessionId;
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8396005..0fc80dd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2895,6 +2895,10 @@
jint offset,
jint size,
std::shared_ptr<C2Buffer> *buffer) {
+ if ((offset + size) > context->capacity()) {
+ ALOGW("extractBufferFromContext: offset + size provided exceed capacity");
+ return;
+ }
*buffer = context->toC2Buffer(offset, size);
if (*buffer == nullptr) {
if (!context->mMemory) {
@@ -2995,18 +2999,15 @@
"MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
- sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
- jint sampleSize = 0;
+ sp<CryptoInfosWrapper> cryptoInfos = nullptr;
+ jint sampleSize = totalSize;
if (cryptoInfoArray != nullptr) {
+ cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
extractCryptoInfosFromObjectArray(env,
&sampleSize,
&cryptoInfos->value,
cryptoInfoArray,
&errorDetailMsg);
- } else {
- sampleSize = totalSize;
- std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)};
- cryptoInfos->value.push_back(std::move(cryptoInfo));
}
if (env->ExceptionCheck()) {
// Creation of cryptoInfo failed. Let the exception bubble up.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 7f3792d..752ebdf 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -98,6 +98,7 @@
"libpowermanager",
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
+ "android.os.flags-aconfig-cc",
"libnativedisplay",
],
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 9d0221a..c0db089 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -268,9 +268,9 @@
ctor public PollingFrame(int, @Nullable byte[], int, int);
method public int describeContents();
method @NonNull public byte[] getData();
- method public int getGain();
method public int getTimestamp();
method public int getType();
+ method public int getVendorSpecificGain();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 994f4ae..29d7bdf 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -157,7 +157,7 @@
mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
mData = (data == null) ? new byte[0] : data;
- mGain = frame.getByte(KEY_POLLING_LOOP_GAIN);
+ mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
}
@@ -194,8 +194,9 @@
/**
* Returns the gain representing the field strength of the NFC field when this polling loop
* frame was observed.
+ * @return the gain or -1 if there is no gain measurement associated with this frame.
*/
- public int getGain() {
+ public int getVendorSpecificGain() {
return mGain;
}
@@ -227,7 +228,9 @@
public Bundle toBundle() {
Bundle frame = new Bundle();
frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
- frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain());
+ if (getVendorSpecificGain() != -1) {
+ frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain());
+ }
frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
return frame;
@@ -236,7 +239,7 @@
@Override
public String toString() {
return "PollingFrame { Type: " + (char) getType()
- + ", gain: " + getGain()
+ + ", gain: " + getVendorSpecificGain()
+ ", timestamp: " + Integer.toUnsignedString(getTimestamp())
+ ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }";
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 4ef7760..af78573 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -336,7 +336,7 @@
if (!footerDescription.isNullOrBlank()) {
item {
Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodySmallText(text = footerDescription)
+ BodyMediumText(text = footerDescription)
}
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index ef40188..e35acae 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -25,6 +25,7 @@
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.RemoteEntryInfo
import com.android.internal.util.Preconditions
+import java.time.Instant
data class GetCredentialUiState(
val isRequestForAllOptions: Boolean,
@@ -156,11 +157,17 @@
userNameToCredentialEntryMap.values.forEach {
it.sortWith(comparator)
}
- // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+ // Transform to list of PerUserNameCredentialEntryLists and then sort the outer list (of
+ // entries grouped by username / entryGroupId) based on the latest timestamp within that
+ // PerUserNameCredentialEntryList
val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
PerUserNameCredentialEntryList(it.key, it.value)
}.sortedWith(
- compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
+ compareByDescending {
+ it.sortedCredentialEntryList.maxByOrNull{ entry ->
+ entry.lastUsedTimeMillis ?: Instant.MIN
+ }?.lastUsedTimeMillis ?: Instant.MIN
+ }
)
return ProviderDisplayInfo(
@@ -211,7 +218,7 @@
val typePriorityMap: Map<String, Int>,
) : Comparator<CredentialEntryInfo> {
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
- // First prefer passkey type for its security benefits
+ // First rank by priorities of each credential type.
if (p0.rawCredentialType != p1.rawCredentialType) {
val p0Priority = typePriorityMap.getOrDefault(
p0.rawCredentialType, PriorityHints.PRIORITY_DEFAULT
@@ -225,6 +232,7 @@
return 1
}
}
+ // Then rank by last used timestamps.
val p0LastUsedTimeMillis = p0.lastUsedTimeMillis
val p1LastUsedTimeMillis = p1.lastUsedTimeMillis
// Then order by last used timestamp
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e489bc5..33086f98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1918,6 +1918,16 @@
}
};
+ public static final AppFilter FILTER_PERSONAL_OR_PRIVATE = new AppFilter() {
+ @Override
+ public void init() {}
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return entry.showInPersonalTab || entry.isPrivateProfile();
+ }
+ };
+
/**
* Displays a combined list with "downloaded" and "visible in launcher" apps only.
*/
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ce0257f..2a8eb9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -72,6 +72,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,6 +158,9 @@
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
+ private static final String APEX_DIR = "/apex";
+ private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
+
/**
* This tag is applied to all aconfig default value-loaded flags.
*/
@@ -238,7 +242,7 @@
private int mNextHistoricalOpIdx;
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private Map<String, Map<String, String>> mNamespaceDefaults;
public static final int SETTINGS_TYPE_GLOBAL = 0;
@@ -332,23 +336,29 @@
mHistoricalOperations = Build.IS_DEBUGGABLE
? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
+ mNamespaceDefaults = new HashMap<>();
+
synchronized (mLock) {
readStateSyncLocked();
if (Flags.loadAconfigDefaults()) {
if (isConfigSettingsKey(mKey)) {
- loadAconfigDefaultValuesLocked();
+ loadAconfigDefaultValuesLocked(sAconfigTextProtoFilesOnDevice);
}
}
+ if (Flags.loadApexAconfigProtobufs()) {
+ if (isConfigSettingsKey(mKey)) {
+ List<String> apexProtoPaths = listApexProtoPaths();
+ loadAconfigDefaultValuesLocked(apexProtoPaths);
+ }
+ }
}
}
@GuardedBy("mLock")
- private void loadAconfigDefaultValuesLocked() {
- mNamespaceDefaults = new HashMap<>();
-
- for (String fileName : sAconfigTextProtoFilesOnDevice) {
+ private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
+ for (String fileName : filePaths) {
try (FileInputStream inputStream = new FileInputStream(fileName)) {
loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
} catch (IOException e) {
@@ -357,13 +367,41 @@
}
}
+ private List<String> listApexProtoPaths() {
+ LinkedList<String> paths = new LinkedList();
+
+ File apexDirectory = new File(APEX_DIR);
+ if (!apexDirectory.isDirectory()) {
+ return paths;
+ }
+
+ File[] subdirs = apexDirectory.listFiles();
+ if (subdirs == null) {
+ return paths;
+ }
+
+ for (File prefix : subdirs) {
+ // For each mainline modules, there are two directories, one <modulepackage>/,
+ // and one <modulepackage>@<versioncode>/. Just read the former.
+ if (prefix.getAbsolutePath().contains("@")) {
+ continue;
+ }
+
+ File protoPath = new File(prefix + APEX_ACONFIG_PATH_SUFFIX);
+ if (!protoPath.exists()) {
+ continue;
+ }
+
+ paths.add(protoPath.getAbsolutePath());
+ }
+ return paths;
+ }
+
@VisibleForTesting
@GuardedBy("mLock")
public void addAconfigDefaultValuesFromMap(
@NonNull Map<String, Map<String, String>> defaultMap) {
- if (mNamespaceDefaults != null) {
- mNamespaceDefaults.putAll(defaultMap);
- }
+ mNamespaceDefaults.putAll(defaultMap);
}
@VisibleForTesting
@@ -447,7 +485,7 @@
return names;
}
- @Nullable
+ @NonNull
public Map<String, Map<String, String>> getAconfigDefaultValues() {
synchronized (mLock) {
return mNamespaceDefaults;
@@ -519,9 +557,9 @@
return false;
}
- // Aconfig flags are always boot stable, so we anytime we write one, we staged it to be
+ // Aconfig flags are always boot stable, so we anytime we write one, we stage it to be
// applied on reboot.
- if (Flags.stageAllAconfigFlags() && mNamespaceDefaults != null) {
+ if (Flags.stageAllAconfigFlags()) {
int slashIndex = name.indexOf("/");
boolean stageFlag = isConfigSettingsKey(mKey)
&& slashIndex != -1
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index e5086e8..2e14e9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -25,3 +25,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "load_apex_aconfig_protobufs"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
+ bug: "327383546"
+ is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index ea30c69..33362a2 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -25,7 +25,6 @@
import android.aconfig.Aconfig.parsed_flag;
import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
-import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -231,38 +230,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_LOAD_ACONFIG_DEFAULTS)
- public void testAddingAconfigMapOnNullIsNoOp() {
- int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
- Object lock = new Object();
- SettingsState settingsState = new SettingsState(
- InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
- parsed_flags flags = parsed_flags
- .newBuilder()
- .addParsedFlag(parsed_flag
- .newBuilder()
- .setPackage("com.android.flags")
- .setName("flag5")
- .setNamespace("test_namespace")
- .setDescription("test flag")
- .addBug("12345678")
- .setState(Aconfig.flag_state.DISABLED)
- .setPermission(Aconfig.flag_permission.READ_WRITE))
- .build();
-
- synchronized (lock) {
- Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
- settingsState.addAconfigDefaultValuesFromMap(defaults);
-
- assertEquals(null, settingsState.getAconfigDefaultValues());
- }
-
- }
-
- @Test
public void testInvalidAconfigProtoDoesNotCrash() {
Map<String, Map<String, String>> defaults = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cc2e84c..04cb88d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -308,6 +308,7 @@
name: "SystemUI-tests",
use_resource_processor: true,
manifest: "tests/AndroidManifest-base.xml",
+ resource_dirs: [],
additional_manifests: ["tests/AndroidManifest.xml"],
srcs: [
"tests/src/**/*.kt",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8c8975f..baa1397 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -194,14 +194,6 @@
}
flag {
- name: "keyguard_shade_migration_nssl"
- namespace: "systemui"
- description: "Moves NSSL into a shared element between the notification_panel and "
- "keyguard_root_view."
- bug: "278054201"
-}
-
-flag {
name: "unfold_animation_background_progress"
namespace: "systemui"
description: "Moves unfold animation progress calculation to a background thread"
@@ -419,6 +411,13 @@
}
flag {
+ name: "smartspace_remoteviews_rendering"
+ namespace: "systemui"
+ description: "Indicate Smartspace RemoteViews rendering"
+ bug: "326292691"
+}
+
+flag {
name: "pin_input_field_styled_focus_state"
namespace: "systemui"
description: "Enables styled focus states on pin input field if keyboard is connected"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index bd539a7..2a13d49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -51,6 +51,7 @@
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@@ -71,6 +72,7 @@
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
+ val selectedUserId by viewModel.selectedUserId.collectAsState()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -87,47 +89,51 @@
val color = MaterialTheme.colorScheme.onSurfaceVariant
val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
- TextField(
- value = password,
- onValueChange = viewModel::onPasswordInputChanged,
- enabled = isInputEnabled,
- visualTransformation = PasswordVisualTransformation(),
- singleLine = true,
- textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
- keyboardOptions =
- KeyboardOptions(
- keyboardType = KeyboardType.Password,
- imeAction = ImeAction.Done,
- ),
- keyboardActions =
- KeyboardActions(
- onDone = { viewModel.onAuthenticateKeyPressed() },
- ),
- modifier =
- modifier
- .focusRequester(focusRequester)
- .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
- .drawBehind {
- drawLine(
- color = color,
- start = Offset(x = 0f, y = size.height - lineWidthPx),
- end = Offset(size.width, y = size.height - lineWidthPx),
- strokeWidth = lineWidthPx,
- )
- }
- .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
- if (keyEvent.key == Key.Back) {
- viewModel.onImeDismissed()
- true
- } else {
- false
+ SelectedUserAwareInputConnection(selectedUserId) {
+ TextField(
+ value = password,
+ onValueChange = viewModel::onPasswordInputChanged,
+ enabled = isInputEnabled,
+ visualTransformation = PasswordVisualTransformation(),
+ singleLine = true,
+ textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+ keyboardOptions =
+ KeyboardOptions(
+ keyboardType = KeyboardType.Password,
+ imeAction = ImeAction.Done,
+ ),
+ keyboardActions =
+ KeyboardActions(
+ onDone = { viewModel.onAuthenticateKeyPressed() },
+ ),
+ modifier =
+ modifier
+ .focusRequester(focusRequester)
+ .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+ .drawBehind {
+ drawLine(
+ color = color,
+ start = Offset(x = 0f, y = size.height - lineWidthPx),
+ end = Offset(size.width, y = size.height - lineWidthPx),
+ strokeWidth = lineWidthPx,
+ )
}
- },
- trailingIcon =
- if (isImeSwitcherButtonVisible) {
- { ImeSwitcherButton(viewModel, color) }
- } else null
- )
+ .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
+ if (keyEvent.key == Key.Back) {
+ viewModel.onImeDismissed()
+ true
+ } else {
+ false
+ }
+ },
+ trailingIcon =
+ if (isImeSwitcherButtonVisible) {
+ { ImeSwitcherButton(viewModel, color) }
+ } else {
+ null
+ }
+ )
+ }
}
/** Button for changing the password input method (IME). */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
new file mode 100644
index 0000000..c8e1450
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package com.android.systemui.common.ui.compose
+
+import android.annotation.UserIdInt
+import android.os.UserHandle
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.InterceptPlatformTextInput
+import androidx.compose.ui.platform.PlatformTextInputMethodRequest
+
+/**
+ * Wrapper for input connection composables that need to be aware of the selected user to connect to
+ * the correct instance of on-device services like autofill, autocorrect, etc.
+ *
+ * Usage:
+ * ```
+ * @Composable
+ * fun YourFunction(viewModel: YourViewModel) {
+ * val selectedUserId by viewModel.selectedUserId.collectAsState()
+ *
+ * SelectedUserAwareInputConnection(selectedUserId) {
+ * TextField(...)
+ * }
+ * }
+ * ```
+ */
+@Composable
+fun SelectedUserAwareInputConnection(
+ @UserIdInt selectedUserId: Int,
+ content: @Composable () -> Unit,
+) {
+ InterceptPlatformTextInput(
+ interceptor = { request, nextHandler ->
+ // Create a new request to wrap the incoming one with some custom logic.
+ val modifiedRequest =
+ object : PlatformTextInputMethodRequest {
+ override fun createInputConnection(outAttributes: EditorInfo): InputConnection {
+ val inputConnection = request.createInputConnection(outAttributes)
+ // After the original request finishes initializing the EditorInfo we can
+ // customize it. If we needed to we could also wrap the InputConnection
+ // before
+ // returning it.
+ updateEditorInfo(outAttributes)
+ return inputConnection
+ }
+
+ fun updateEditorInfo(outAttributes: EditorInfo) {
+ outAttributes.targetInputMethodUser = UserHandle.of(selectedUserId)
+ }
+ }
+
+ // Send our wrapping request to the next handler, which could be the system or another
+ // interceptor up the tree.
+ nextHandler.startInputMethod(modifiedRequest)
+ }
+ ) {
+ content()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 63abc8f..0ebcf56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.DockManagerFake
import com.android.systemui.flags.FakeFeatureFlags
@@ -49,6 +50,7 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
@@ -57,6 +59,7 @@
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -80,6 +83,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -179,6 +183,7 @@
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = withDeps.keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -193,6 +198,8 @@
backgroundDispatcher = testDispatcher,
appContext = context,
)
+
+ whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
}
@Test
@@ -339,6 +346,25 @@
}
@Test
+ fun quickAffordance_updateOncePerShadeExpansion() =
+ testScope.runTest {
+ val shadeExpansion = MutableStateFlow(0f)
+ whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
+
+ val collectedValue by
+ collectValues(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+
+ val initialSize = collectedValue.size
+ for (i in 0..10) {
+ shadeExpansion.value = i / 10f
+ }
+
+ assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+ }
+
+ @Test
fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
testScope.runTest {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 8853589..33f14d4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -22,17 +22,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
@@ -47,7 +43,6 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -58,7 +53,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
@@ -70,6 +64,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+
/**
* Class to handle ugly dependencies throughout sysui until we determine the
* long-term dependency injection solution.
@@ -96,10 +91,6 @@
* Key for getting a Handler for receiving time tick broadcasts on.
*/
public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler";
- /**
- * Generic handler on the main thread.
- */
- private static final String MAIN_HANDLER_NAME = "main_handler";
/**
* An email address to send memory leak reports to by default.
@@ -121,11 +112,6 @@
*/
public static final DependencyKey<Handler> TIME_TICK_HANDLER =
new DependencyKey<>(TIME_TICK_HANDLER_NAME);
- /**
- * Generic handler on the main thread.
- */
- public static final DependencyKey<Handler> MAIN_HANDLER =
- new DependencyKey<>(MAIN_HANDLER_NAME);
private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
@@ -134,7 +120,6 @@
@Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
@Inject Lazy<BluetoothController> mBluetoothController;
- @Inject Lazy<FlashlightController> mFlashlightController;
@Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
@Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController;
@Inject Lazy<PluginManager> mPluginManager;
@@ -150,15 +135,10 @@
@Inject Lazy<LightBarController> mLightBarController;
@Inject Lazy<OverviewProxyService> mOverviewProxyService;
@Inject Lazy<NavigationModeController> mNavBarModeController;
- @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
- @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
- @Inject Lazy<IStatusBarService> mIStatusBarService;
- @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
@Inject Lazy<NavigationBarController> mNavigationBarController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
@Inject @Background Lazy<Looper> mBgLooper;
- @Inject @Main Lazy<Handler> mMainHandler;
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<CommandQueue> mCommandQueue;
@@ -187,10 +167,8 @@
// on imports.
mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
mProviders.put(BG_LOOPER, mBgLooper::get);
- mProviders.put(MAIN_HANDLER, mMainHandler::get);
mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
mProviders.put(BluetoothController.class, mBluetoothController::get);
- mProviders.put(FlashlightController.class, mFlashlightController::get);
mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get);
mProviders.put(PluginManager.class, mPluginManager::get);
@@ -205,13 +183,6 @@
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
mProviders.put(NavigationModeController.class, mNavBarModeController::get);
- mProviders.put(AccessibilityButtonModeObserver.class,
- mAccessibilityButtonModeObserver::get);
- mProviders.put(AccessibilityButtonTargetsObserver.class,
- mAccessibilityButtonListController::get);
- mProviders.put(IStatusBarService.class, mIStatusBarService::get);
- mProviders.put(NotificationRemoteInputManager.Callback.class,
- mNotificationRemoteInputManagerCallback::get);
mProviders.put(NavigationBarController.class, mNavigationBarController::get);
mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 1c8b84d..b42eda1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -73,6 +73,14 @@
initialValue = isInputEnabled.value && !isTextFieldFocused.value,
)
+ /** The ID of the currently-selected user. */
+ val selectedUserId: StateFlow<Int> =
+ selectedUserInteractor.selectedUser.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = selectedUserInteractor.getSelectedUserId(),
+ )
+
override fun onHidden() {
super.onHidden()
isTextFieldFocused.value = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index acfa107..28282c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -57,7 +58,8 @@
override fun start() {
listenForDreamingToOccluded()
- listenForDreamingToGone()
+ listenForDreamingToGoneWhenDismissable()
+ listenForDreamingToGoneFromBiometricUnlock()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
listenForDreamingToGlanceableHub()
@@ -101,7 +103,29 @@
}
}
- private fun listenForDreamingToGone() {
+ private fun listenForDreamingToGoneWhenDismissable() {
+ scope.launch {
+ keyguardInteractor.isAbleToDream
+ .sampleCombine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardInteractor.isKeyguardDismissible,
+ startedKeyguardTransitionStep,
+ )
+ .collect {
+ (isDreaming, isKeyguardShowing, isKeyguardDismissible, lastStartedTransition) ->
+ if (
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING &&
+ isKeyguardDismissible &&
+ !isKeyguardShowing
+ ) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToGoneFromBiometricUnlock() {
scope.launch {
keyguardInteractor.biometricUnlockState
.sample(startedKeyguardTransitionStep, ::Pair)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index b0a3881..8eb1a50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -46,6 +46,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -55,6 +56,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -66,6 +68,7 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
+ private val shadeInteractor: ShadeInteractor,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -100,9 +103,10 @@
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
+ shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
biometricSettingsRepository.isCurrentUserInLockdown,
- ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
- if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+ if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
deleted file mode 100644
index fc45228..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.mediaprojection.devicepolicy
-
-import android.content.Context
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
-
-/** Dialog that shows that screen capture is disabled on this device. */
-class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
-
- init {
- setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
- setMessage(
- context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
- )
- setIcon(R.drawable.ic_cast)
- setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
new file mode 100644
index 0000000..8aed535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialogDelegate @Inject constructor(
+ @Main private val resources: Resources,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog {
+ val dialog = systemUIDialogFactory.create(this)
+ dialog.setTitle(resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+ dialog.setMessage(
+ resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+ )
+ dialog.setIcon(R.drawable.ic_cast)
+ dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) {
+ _, _ -> dialog.cancel()
+ }
+
+ return dialog
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..17f9caf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -59,7 +59,7 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -79,6 +79,7 @@
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
private final StatusBarManager mStatusBarManager;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
private String mPackageName;
private int mUid;
@@ -93,14 +94,17 @@
private boolean mUserSelectingTask = false;
@Inject
- public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+ public MediaProjectionPermissionActivity(
+ FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
StatusBarManager statusBarManager,
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
}
@Override
@@ -315,10 +319,7 @@
final UserHandle hostUserHandle = getHostUserHandle();
if (mScreenCaptureDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(hostUserHandle)) {
- // Using application context for the dialog, instead of the activity context, so we get
- // the correct screen width when in split screen.
- Context dialogContext = getApplicationContext();
- AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext);
+ AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createDialog();
setUpDialog(dialog);
dialog.show();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
index cc8cc51..d6629e0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -18,14 +18,18 @@
import android.app.ActivityManager.RunningTaskInfo
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.withContext
class TaskSwitcherNotificationViewModel
@@ -36,21 +40,30 @@
) {
val uiState: Flow<TaskSwitcherNotificationUiState> =
- interactor.taskSwitchChanges.map { taskSwitchChange ->
- Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
- when (taskSwitchChange) {
- is TaskSwitchState.TaskSwitched -> {
- TaskSwitcherNotificationUiState.Showing(
- projectedTask = taskSwitchChange.projectedTask,
- foregroundTask = taskSwitchChange.foregroundTask,
- )
- }
- is TaskSwitchState.NotProjectingTask,
- is TaskSwitchState.TaskUnchanged -> {
- TaskSwitcherNotificationUiState.NotShowing
+ interactor.taskSwitchChanges
+ .map { taskSwitchChange ->
+ Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+ when (taskSwitchChange) {
+ is TaskSwitchState.TaskSwitched -> {
+ TaskSwitcherNotificationUiState.Showing(
+ projectedTask = taskSwitchChange.projectedTask,
+ foregroundTask = taskSwitchChange.foregroundTask,
+ )
+ }
+ is TaskSwitchState.NotProjectingTask,
+ is TaskSwitchState.TaskUnchanged -> {
+ TaskSwitcherNotificationUiState.NotShowing
+ }
}
}
- }
+ .transformLatest { uiState ->
+ emit(uiState)
+ if (uiState is TaskSwitcherNotificationUiState.Showing) {
+ delay(NOTIFICATION_MAX_SHOW_DURATION)
+ Log.d(TAG, "Auto hiding notification after $NOTIFICATION_MAX_SHOW_DURATION")
+ emit(TaskSwitcherNotificationUiState.NotShowing)
+ }
+ }
suspend fun onSwitchTaskClicked(task: RunningTaskInfo) {
interactor.switchProjectedTask(task)
@@ -60,6 +73,7 @@
withContext(backgroundDispatcher) { interactor.goBackToTask(task) }
companion object {
+ @VisibleForTesting val NOTIFICATION_MAX_SHOW_DURATION = 5.seconds
private const val TAG = "TaskSwitchNotifVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 152f193..9f7d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
@@ -28,7 +29,6 @@
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -70,9 +70,11 @@
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
+
@SysUISingleton
public class NavigationBarControllerImpl implements
ConfigurationController.ConfigurationListener,
@@ -82,7 +84,7 @@
private static final String TAG = NavigationBarControllerImpl.class.getSimpleName();
private final Context mContext;
- private final Handler mHandler;
+ private final Executor mExecutor;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
private final SecureSettings mSecureSettings;
private final DisplayTracker mDisplayTracker;
@@ -119,7 +121,7 @@
NavigationModeController navigationModeController,
SysUiState sysUiFlagsContainer,
CommandQueue commandQueue,
- @Main Handler mainHandler,
+ @Main Executor mainExecutor,
ConfigurationController configurationController,
NavBarHelper navBarHelper,
TaskbarDelegate taskbarDelegate,
@@ -133,7 +135,7 @@
SecureSettings secureSettings,
DisplayTracker displayTracker) {
mContext = context;
- mHandler = mainHandler;
+ mExecutor = mainExecutor;
mNavigationBarComponentFactory = navigationBarComponentFactory;
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
@@ -193,7 +195,7 @@
mNavMode = mode;
updateAccessibilityButtonModeIfNeeded();
- mHandler.post(() -> {
+ mExecutor.execute(() -> {
// create/destroy nav bar based on nav mode only in unfolded state
if (oldMode != mNavMode) {
updateNavbarForTaskbar();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 0dd0a60..52cf4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -481,7 +481,8 @@
mSecondaryMobileTitleText.setTextAppearance(
R.style.TextAppearance_InternetDialog_Active);
- TextView mSecondaryMobileSummaryText = mDialogView.requireViewById(R.id.secondary_mobile_summary);
+ TextView mSecondaryMobileSummaryText =
+ mDialogView.requireViewById(R.id.secondary_mobile_summary);
summary = getMobileNetworkSummary(autoSwitchNonDdsSubId);
if (!TextUtils.isEmpty(summary)) {
mSecondaryMobileSummaryText.setText(
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 80f11f1..1c07d00 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -40,7 +40,7 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
@@ -65,6 +65,7 @@
private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
+ private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
@Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {
@@ -124,7 +125,7 @@
.isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
) {
mainExecutor.execute {
- ScreenCaptureDisabledDialog(context).show()
+ screenCaptureDisabledDialogDelegate.createDialog().show()
screenRecordSwitch.isChecked = false
}
return@execute
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index a4ba2a2..8fe84c9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -42,11 +42,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.CallbackController;
import dagger.Lazy;
@@ -71,18 +69,19 @@
private CountDownTimer mCountDownTimer = null;
private final Executor mMainExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final Context mContext;
private final FeatureFlags mFlags;
- private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
- private final SystemUIDialog.Factory mDialogFactory;
+ private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+ private final ScreenRecordPermissionDialogDelegate.Factory
+ mScreenRecordPermissionDialogDelegateFactory;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
protected static final String EXTRA_STATE = "extra_state";
- private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
+ private final CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
new CopyOnWriteArrayList<>();
private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
@@ -115,24 +114,26 @@
* Create a new RecordingController
*/
@Inject
- public RecordingController(@Main Executor mainExecutor,
+ public RecordingController(
+ @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher,
- Context context,
FeatureFlags flags,
- UserContextProvider userContextProvider,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
- SystemUIDialog.Factory dialogFactory) {
+ ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
+ ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
+ ScreenRecordPermissionDialogDelegate.Factory
+ screenRecordPermissionDialogDelegateFactory) {
mMainExecutor = mainExecutor;
- mContext = context;
mFlags = flags;
mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
- mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
- mDialogFactory = dialogFactory;
+ mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
+ mScreenRecordDialogFactory = screenRecordDialogFactory;
+ mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -163,27 +164,18 @@
if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
&& mDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
- return new ScreenCaptureDisabledDialog(mContext);
+ return mScreenCaptureDisabledDialogDelegate.createDialog();
}
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mDialogFactory.create(new ScreenRecordPermissionDialogDelegate(
- getHostUserHandle(),
- getHostUid(),
- /* controller= */ this,
- activityStarter,
- mUserContextProvider,
- onStartRecordingClicked,
- mMediaProjectionMetricsLogger,
- mDialogFactory))
- : new ScreenRecordDialog(
- context,
- /* controller= */ this,
- mUserContextProvider,
- onStartRecordingClicked);
+ return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
+ ? mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
+ : mScreenRecordDialogFactory
+ .create(this, onStartRecordingClicked))
+ .createDialog();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
rename to packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
index b98093e..9f1447b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
@@ -49,52 +49,69 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.util.Arrays;
import java.util.List;
/**
* Dialog to select screen recording options
*/
-public class ScreenRecordDialog extends SystemUIDialog {
+public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate {
private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
MIC_AND_INTERNAL);
private static final long DELAY_MS = 3000;
private static final long INTERVAL_MS = 1000;
- private final RecordingController mController;
+ private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final UserContextProvider mUserContextProvider;
- @Nullable
+ private final RecordingController mController;
private final Runnable mOnStartRecordingClicked;
private Switch mTapsSwitch;
private Switch mAudioSwitch;
private Spinner mOptions;
- public ScreenRecordDialog(Context context,
- RecordingController controller,
- UserContextProvider userContextProvider,
- @Nullable Runnable onStartRecordingClicked) {
- super(context);
- mController = controller;
+ @AssistedFactory
+ public interface Factory {
+ ScreenRecordDialogDelegate create(
+ RecordingController recordingController,
+ @Nullable Runnable onStartRecordingClicked
+ );
+ }
+
+ @AssistedInject
+ public ScreenRecordDialogDelegate(
+ SystemUIDialog.Factory systemUIDialogFactory,
+ UserContextProvider userContextProvider,
+ @Assisted RecordingController controller,
+ @Assisted @Nullable Runnable onStartRecordingClicked) {
+ mSystemUIDialogFactory = systemUIDialogFactory;
mUserContextProvider = userContextProvider;
+ mController = controller;
mOnStartRecordingClicked = onStartRecordingClicked;
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ public SystemUIDialog createDialog() {
+ return mSystemUIDialogFactory.create(this);
+ }
- Window window = getWindow();
+ @Override
+ public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {
+ Window window = dialog.getWindow();
window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
window.setGravity(Gravity.CENTER);
- setTitle(R.string.screenrecord_title);
+ dialog.setTitle(R.string.screenrecord_title);
- setContentView(R.layout.screen_record_dialog);
+ dialog.setContentView(R.layout.screen_record_dialog);
- TextView cancelBtn = findViewById(R.id.button_cancel);
- cancelBtn.setOnClickListener(v -> dismiss());
- TextView startBtn = findViewById(R.id.button_start);
+ TextView cancelBtn = dialog.findViewById(R.id.button_cancel);
+ cancelBtn.setOnClickListener(v -> dialog.dismiss());
+ TextView startBtn = dialog.findViewById(R.id.button_start);
startBtn.setOnClickListener(v -> {
if (mOnStartRecordingClicked != null) {
// Note that it is important to run this callback before dismissing, so that the
@@ -104,13 +121,13 @@
// Start full-screen recording
requestScreenCapture(/* captureTarget= */ null);
- dismiss();
+ dialog.dismiss();
});
- mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
- mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
- mOptions = findViewById(R.id.screen_recording_options);
- ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
+ mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch);
+ mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch);
+ mOptions = dialog.findViewById(R.id.screen_recording_options);
+ ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(),
android.R.layout.simple_spinner_dropdown_item,
MODES);
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index 3eb26f4..ba775cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -46,17 +46,20 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/** Dialog to select screen recording options */
-class ScreenRecordPermissionDialogDelegate(
- private val hostUserHandle: UserHandle,
- private val hostUid: Int,
- private val controller: RecordingController,
+class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
+ @Assisted private val hostUserHandle: UserHandle,
+ @Assisted private val hostUid: Int,
+ @Assisted private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
- private val onStartRecordingClicked: Runnable?,
+ @Assisted private val onStartRecordingClicked: Runnable?,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
- private val systemUIDialogFactory: SystemUIDialog.Factory
+ private val systemUIDialogFactory: SystemUIDialog.Factory,
) :
BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
createOptionList(),
@@ -65,8 +68,19 @@
mediaProjectionMetricsLogger,
R.drawable.ic_screenrecord,
R.color.screenrecord_icon_color
- ),
- SystemUIDialog.Delegate {
+ ), SystemUIDialog.Delegate {
+
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ recordingController: RecordingController,
+ hostUserHandle: UserHandle,
+ hostUid: Int,
+ onStartRecordingClicked: Runnable?
+ ): ScreenRecordPermissionDialogDelegate
+ }
+
private lateinit var tapsSwitch: Switch
private lateinit var tapsSwitchContainer: ViewGroup
private lateinit var tapsView: View
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1566de5..2fd438b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -351,7 +351,7 @@
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
private final NotificationGutsManager mGutsManager;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
- private final QuickSettingsController mQsController;
+ private final QuickSettingsControllerImpl mQsController;
private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
private final TouchHandler mTouchHandler = new TouchHandler();
@@ -726,7 +726,7 @@
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
NavigationBarController navigationBarController,
- QuickSettingsController quickSettingsController,
+ QuickSettingsControllerImpl quickSettingsController,
FragmentService fragmentService,
IStatusBarService statusBarService,
ContentResolver contentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
new file mode 100644
index 0000000..c96a339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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.shade
+
+interface QuickSettingsController {
+ /** Returns whether or not QuickSettings is expanded. */
+ val expanded: Boolean
+
+ /** Returns whether or not QuickSettings is being customized. */
+ val isCustomizing: Boolean
+
+ /** Returns Whether we should intercept a gesture to open Quick Settings. */
+ @Deprecated("specific to legacy touch handling")
+ fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean
+
+ /** Closes the Qs customizer. */
+ fun closeQsCustomizer()
+
+ /**
+ * This method closes QS but in split shade it should be used only in special cases: to make
+ * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing from
+ * split shade
+ */
+ @Deprecated("specific to legacy split shade") fun closeQs()
+
+ /** Calculate top padding for notifications */
+ @Deprecated("specific to legacy DebugDrawable")
+ fun calculateNotificationsTopPadding(
+ isShadeExpanding: Boolean,
+ keyguardNotificationStaticPadding: Int,
+ expandedFraction: Float,
+ ): Float
+
+ /** Calculate height of QS panel */
+ @Deprecated("specific to legacy DebugDrawable")
+ fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
rename to packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index a5c0553..19d98a0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -118,7 +118,7 @@
* TODO (b/264460656) make this dumpable
*/
@SysUISingleton
-public class QuickSettingsController implements Dumpable {
+public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
public static final String TAG = "QuickSettingsController";
public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100;
@@ -252,7 +252,7 @@
/**
* The window width currently in effect -- used together with
- * {@link QuickSettingsController#mCachedGestureInsets} to decide whether a back gesture should
+ * {@link QuickSettingsControllerImpl#mCachedGestureInsets} to decide whether a back gesture should
* receive a horizontal swipe inwards from the left/right vertical edge of the screen.
* We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
*/
@@ -304,7 +304,7 @@
private final QS.ScrollListener mQsScrollListener = this::onScroll;
@Inject
- public QuickSettingsController(
+ public QuickSettingsControllerImpl(
Lazy<NotificationPanelViewController> panelViewControllerLazy,
NotificationPanelView panelView,
QsFrameTranslateController qsFrameTranslateController,
@@ -399,23 +399,23 @@
mQs = qs;
}
- public void setExpansionHeightListener(ExpansionHeightListener listener) {
+ void setExpansionHeightListener(ExpansionHeightListener listener) {
mExpansionHeightListener = listener;
}
- public void setQsStateUpdateListener(QsStateUpdateListener listener) {
+ void setQsStateUpdateListener(QsStateUpdateListener listener) {
mQsStateUpdateListener = listener;
}
- public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
+ void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
mApplyClippingImmediatelyListener = listener;
}
- public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
+ void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
mFlingQsWithoutClickListener = listener;
}
- public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
+ void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
mExpansionHeightSetToMaxListener = callback;
}
@@ -507,15 +507,11 @@
&& mPanelView.getRootWindowInsets().isVisible(ime());
}
- public boolean isExpansionEnabled() {
+ boolean isExpansionEnabled() {
return mExpansionEnabledPolicy && mExpansionEnabledAmbient
&& !isRemoteInputActiveWithKeyboardUp();
}
- public float getTransitioningToFullShadeProgress() {
- return mTransitioningToFullShadeProgress;
- }
-
/** */
@VisibleForTesting
boolean isExpandImmediate() {
@@ -536,7 +532,7 @@
* Computes (and caches) the gesture insets for the current window. Intended to be called
* on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
*/
- public void updateGestureInsetsCache() {
+ void updateGestureInsetsCache() {
WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
@@ -548,7 +544,7 @@
* Returns whether x coordinate lies in the vertical edges of the screen
* (the only place where a back gesture can be initiated).
*/
- public boolean shouldBackBypassQuickSettings(float touchX) {
+ boolean shouldBackBypassQuickSettings(float touchX) {
return (touchX < mCachedGestureInsets.left)
|| (touchX > mCachedWindowWidth - mCachedGestureInsets.right);
}
@@ -592,6 +588,7 @@
return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
}
+ @Override
public boolean getExpanded() {
return mShadeRepository.getLegacyIsQsExpanded().getValue();
}
@@ -601,7 +598,7 @@
return mShadeRepository.getLegacyQsTracking().getValue();
}
- public boolean getFullyExpanded() {
+ boolean getFullyExpanded() {
return mFullyExpanded;
}
@@ -623,27 +620,28 @@
return mQs != null;
}
+ @Override
public boolean isCustomizing() {
return isQsFragmentCreated() && mQs.isCustomizing();
}
- public float getExpansionHeight() {
+ float getExpansionHeight() {
return mExpansionHeight;
}
- public boolean getExpandedWhenExpandingStarted() {
+ boolean getExpandedWhenExpandingStarted() {
return mExpandedWhenExpandingStarted;
}
- public int getMinExpansionHeight() {
+ int getMinExpansionHeight() {
return mMinExpansionHeight;
}
- public boolean isFullyExpandedAndTouchesDisallowed() {
+ boolean isFullyExpandedAndTouchesDisallowed() {
return isQsFragmentCreated() && getFullyExpanded() && disallowTouches();
}
- public int getMaxExpansionHeight() {
+ int getMaxExpansionHeight() {
return mMaxExpansionHeight;
}
@@ -654,13 +652,14 @@
return !mTouchAboveFalsingThreshold;
}
- public int getFalsingThreshold() {
+ int getFalsingThreshold() {
return mFalsingThreshold;
}
/**
* Returns Whether we should intercept a gesture to open Quick Settings.
*/
+ @Override
public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
boolean keyguardShowing = mBarState == KEYGUARD;
if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing
@@ -717,7 +716,7 @@
* @param downY the y location where the touch started
* Returns true if the panel could be collapsed because it stared on QQS
*/
- public boolean canPanelCollapseOnQQS(float downX, float downY) {
+ boolean canPanelCollapseOnQQS(float downX, float downY) {
if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
return false;
}
@@ -727,6 +726,7 @@
}
/** Closes the Qs customizer. */
+ @Override
public void closeQsCustomizer() {
if (mQs != null) {
mQs.closeCustomizer();
@@ -734,7 +734,7 @@
}
/** Returns whether touches from the notification panel should be disallowed */
- public boolean disallowTouches() {
+ boolean disallowTouches() {
if (mQs != null) {
return mQs.disallowPanelTouches();
} else {
@@ -754,15 +754,11 @@
}
}
- public void setDozing(boolean dozing) {
+ void setDozing(boolean dozing) {
mDozing = dozing;
}
- /**
- * This method closes QS but in split shade it should be used only in special cases: to make
- * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing
- * from split shade
- */
+ @Override
public void closeQs() {
if (mSplitShadeEnabled) {
mShadeLog.d("Closing QS while in split shade");
@@ -794,7 +790,7 @@
}
/** update Qs height state */
- public void setExpansionHeight(float height) {
+ void setExpansionHeight(float height) {
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
@@ -817,7 +813,7 @@
}
/** */
- public void setHeightOverrideToDesiredHeight() {
+ void setHeightOverrideToDesiredHeight() {
if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
mQs.setHeightOverride(mQs.getDesiredHeight());
}
@@ -919,7 +915,7 @@
}
/** Sets Qs ScrimEnabled and updates QS state. */
- public void setScrimEnabled(boolean scrimEnabled) {
+ void setScrimEnabled(boolean scrimEnabled) {
boolean changed = mScrimEnabled != scrimEnabled;
mScrimEnabled = scrimEnabled;
if (changed) {
@@ -995,7 +991,7 @@
}
/** update expanded state of QS */
- public void updateExpansion() {
+ void updateExpansion() {
if (mQs == null) return;
final float squishiness;
if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) {
@@ -1053,13 +1049,13 @@
// mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
// transition. If that's not the case we should follow QS expansion fraction for when
// user is pulling from the same top to go directly to expanded QS
- return getTransitioningToFullShadeProgress() > 0
+ return mTransitioningToFullShadeProgress > 0
? mLockscreenShadeTransitionController.getQSDragProgress()
: computeExpansionFraction();
}
/** */
- public void updateExpansionEnabledAmbient() {
+ void updateExpansionEnabledAmbient() {
final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
mExpansionEnabledAmbient = mSplitShadeEnabled
|| (mAmbientState.getScrollY() <= scrollRangeToTop);
@@ -1081,7 +1077,7 @@
}
/** Calculate fraction of current QS expansion state */
- public float computeExpansionFraction() {
+ float computeExpansionFraction() {
if (mAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
// be larger than 0 because of the timing, leading to flickers.
@@ -1112,7 +1108,7 @@
}
/** Called when shade starts expanding. */
- public void onExpandingStarted(boolean qsFullyExpanded) {
+ void onExpandingStarted(boolean qsFullyExpanded) {
mNotificationStackScrollLayoutController.onExpansionStarted();
mExpandedWhenExpandingStarted = qsFullyExpanded;
mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
@@ -1363,7 +1359,7 @@
}
}
- /** Calculate top padding for notifications */
+ @Override
public float calculateNotificationsTopPadding(boolean isShadeExpanding,
int keyguardNotificationStaticPadding, float expandedFraction) {
float topPadding;
@@ -1404,7 +1400,7 @@
}
}
- /** Calculate height of QS panel */
+ @Override
public int calculatePanelHeightExpanded(int stackScrollerPadding) {
float
notificationHeight =
@@ -1576,7 +1572,6 @@
}
}
- @VisibleForTesting
boolean isTrackingBlocked() {
return mConflictingExpansionGesture && getExpanded();
}
@@ -1591,7 +1586,7 @@
}
/** handles touches in Qs panel area */
- public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
+ boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
boolean isShadeOrQsHeightAnimationRunning) {
if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
return false;
@@ -1747,7 +1742,7 @@
}
/** intercepts touches on Qs panel area. */
- public boolean onIntercept(MotionEvent event) {
+ boolean onIntercept(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -1858,7 +1853,7 @@
*
* @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
*/
- public void animateCloseQs(boolean animateAway) {
+ void animateCloseQs(boolean animateAway) {
if (mExpansionAnimator != null) {
if (!mAnimatorExpand) {
return;
@@ -1877,7 +1872,7 @@
}
/** @see #flingQs(float, int, Runnable, boolean) */
- public void flingQs(float vel, int type) {
+ void flingQs(float vel, int type) {
flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
@@ -2144,7 +2139,7 @@
}
/** */
- public FragmentHostManager.FragmentListener getQsFragmentListener() {
+ FragmentHostManager.FragmentListener getQsFragmentListener() {
return new QsFragmentListener();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
new file mode 100644
index 0000000..b8250cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.shade
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickSettingsControllerSceneImpl
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ private val qsSceneAdapter: QSSceneAdapter,
+ private val qsContainerController: QSContainerController,
+) : QuickSettingsController {
+
+ override val expanded: Boolean
+ get() = shadeInteractor.isQsExpanded.value
+
+ override val isCustomizing: Boolean
+ get() = qsSceneAdapter.isCustomizing.value
+
+ @Deprecated("specific to legacy touch handling")
+ override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun closeQsCustomizer() {
+ qsContainerController.setCustomizerShowing(false)
+ }
+
+ @Deprecated("specific to legacy split shade")
+ override fun closeQs() {
+ // Do nothing
+ }
+
+ @Deprecated("specific to legacy DebugDrawable")
+ override fun calculateNotificationsTopPadding(
+ isShadeExpanding: Boolean,
+ keyguardNotificationStaticPadding: Int,
+ expandedFraction: Float
+ ): Float {
+ throw UnsupportedOperationException()
+ }
+
+ @Deprecated("specific to legacy DebugDrawable")
+ override fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int {
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 5632766..504dbfd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.data.repository.PrivacyChipRepository
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
@@ -111,6 +113,26 @@
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun provideQuickSettingsController(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
+ sceneContainerOff: Provider<QuickSettingsControllerImpl>,
+ ): QuickSettingsController {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun providesQSContainerController(impl: QSSceneAdapterImpl): QSContainerController {
+ return impl
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index d308fa5..c17ee39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -833,143 +833,143 @@
@Nullable InflationCallback endListener, NotificationEntry entry,
ExpandableNotificationRow row, NotificationContentInflaterLogger logger) {
Assert.isMainThread();
+ if (!runningInflations.isEmpty()) {
+ return false;
+ }
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
- if (runningInflations.isEmpty()) {
- logger.logAsyncTaskProgress(entry, "finishing");
- boolean setRepliesAndActions = true;
- if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
- if (result.inflatedContentView != null) {
- // New view case
- privateLayout.setContractedChild(result.inflatedContentView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
- result.newContentView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
- // Reinflation case. Only update if it's still cached (i.e. view has not been
- // freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
- result.newContentView);
- }
- setRepliesAndActions = true;
+ logger.logAsyncTaskProgress(entry, "finishing");
+ boolean setRepliesAndActions = true;
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+ if (result.inflatedContentView != null) {
+ // New view case
+ privateLayout.setContractedChild(result.inflatedContentView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
+ // Reinflation case. Only update if it's still cached (i.e. view has not been
+ // freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
}
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
- if (result.inflatedExpandedView != null) {
- privateLayout.setExpandedChild(result.inflatedExpandedView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
- result.newExpandedView);
- } else if (result.newExpandedView == null) {
- privateLayout.setExpandedChild(null);
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
- result.newExpandedView);
- }
- if (result.newExpandedView != null) {
- privateLayout.setExpandedInflatedSmartReplies(
- result.expandedInflatedSmartReplies);
- } else {
- privateLayout.setExpandedInflatedSmartReplies(null);
- }
- row.setExpandable(result.newExpandedView != null);
- setRepliesAndActions = true;
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
- if (result.inflatedHeadsUpView != null) {
- privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
- result.newHeadsUpView);
- } else if (result.newHeadsUpView == null) {
- privateLayout.setHeadsUpChild(null);
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
- result.newHeadsUpView);
- }
- if (result.newHeadsUpView != null) {
- privateLayout.setHeadsUpInflatedSmartReplies(
- result.headsUpInflatedSmartReplies);
- } else {
- privateLayout.setHeadsUpInflatedSmartReplies(null);
- }
- setRepliesAndActions = true;
- }
-
- if (AsyncHybridViewInflation.isEnabled()
- && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
- HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
- SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
- if (viewHolder != null && viewModel != null) {
- if (viewModel.isConversation()) {
- SingleLineConversationViewBinder.bind(
- result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder
- );
- } else {
- SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder);
- }
- privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
- }
- }
-
- if (setRepliesAndActions) {
- privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
- if (result.inflatedPublicView != null) {
- publicLayout.setContractedChild(result.inflatedPublicView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
- result.newPublicView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
- result.newPublicView);
- }
- }
-
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
- if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
- if (result.mInflatedGroupHeaderView != null) {
- row.setIsLowPriority(false);
- row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
- remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
- // Re-inflation case. Only update if it's still cached (i.e. view has not
- // been freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- }
- }
-
- if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
- if (result.mInflatedLowPriorityGroupHeaderView != null) {
- // New view case, set row to low priority
- row.setIsLowPriority(true);
- row.setLowPriorityGroupHeader(
- /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
- remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
- result.mNewLowPriorityGroupHeaderView);
- } else if (remoteViewCache.hasCachedView(entry,
- FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
- // Re-inflation case. Only update if it's still cached (i.e. view has not
- // been freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- }
- }
- }
-
- entry.headsUpStatusBarText = result.headsUpStatusBarText;
- entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
- if (endListener != null) {
- endListener.onAsyncInflationFinished(entry);
- }
- return true;
+ setRepliesAndActions = true;
}
- return false;
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+ if (result.inflatedExpandedView != null) {
+ privateLayout.setExpandedChild(result.inflatedExpandedView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
+ } else if (result.newExpandedView == null) {
+ privateLayout.setExpandedChild(null);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
+ }
+ if (result.newExpandedView != null) {
+ privateLayout.setExpandedInflatedSmartReplies(
+ result.expandedInflatedSmartReplies);
+ } else {
+ privateLayout.setExpandedInflatedSmartReplies(null);
+ }
+ row.setExpandable(result.newExpandedView != null);
+ setRepliesAndActions = true;
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+ if (result.inflatedHeadsUpView != null) {
+ privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
+ } else if (result.newHeadsUpView == null) {
+ privateLayout.setHeadsUpChild(null);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
+ }
+ if (result.newHeadsUpView != null) {
+ privateLayout.setHeadsUpInflatedSmartReplies(
+ result.headsUpInflatedSmartReplies);
+ } else {
+ privateLayout.setHeadsUpInflatedSmartReplies(null);
+ }
+ setRepliesAndActions = true;
+ }
+
+ if (AsyncHybridViewInflation.isEnabled()
+ && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+ SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+ if (viewHolder != null && viewModel != null) {
+ if (viewModel.isConversation()) {
+ SingleLineConversationViewBinder.bind(
+ result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder
+ );
+ } else {
+ SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder);
+ }
+ privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+ }
+ }
+
+ if (setRepliesAndActions) {
+ privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+ if (result.inflatedPublicView != null) {
+ publicLayout.setContractedChild(result.inflatedPublicView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ }
+ }
+
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+ if (result.mInflatedGroupHeaderView != null) {
+ row.setIsLowPriority(false);
+ row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
+ remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ }
+ }
+
+ if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+ if (result.mInflatedLowPriorityGroupHeaderView != null) {
+ // New view case, set row to low priority
+ row.setIsLowPriority(true);
+ row.setLowPriorityGroupHeader(
+ /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
+ remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.mNewLowPriorityGroupHeaderView);
+ } else if (remoteViewCache.hasCachedView(entry,
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ }
+ }
+ }
+
+ entry.headsUpStatusBarText = result.headsUpStatusBarText;
+ entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+ if (endListener != null) {
+ endListener.onAsyncInflationFinished(entry);
+ }
+ return true;
}
private static RemoteViews createExpandedView(Notification.Builder builder,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 560d5ba..48d3157 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -315,8 +315,7 @@
mMetricsLogger.count("panel_open", 1);
} else if (!mQsController.getExpanded()
&& !mShadeViewController.isExpandingOrCollapsing()) {
- mQsController.flingQs(0 /* velocity */,
- ShadeViewController.FLING_EXPAND);
+ mShadeController.animateExpandQs();
mMetricsLogger.count("panel_open_qs", 1);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index f12a09b..82d9fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,7 +93,7 @@
/**
* @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to
- * {@link Factory#create(DialogDelegate)} to create a custom dialog.
+ * {@link Factory#create(Delegate)} to create a custom dialog.
*/
@Deprecated
public SystemUIDialog(Context context) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 24a5e80..2e04007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,14 +15,11 @@
package com.android.systemui;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import android.os.Looper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.statusbar.policy.FlashlightController;
-
import org.junit.Assert;
import org.junit.Test;
@@ -33,9 +30,9 @@
@Test
public void testClassDependency() {
- FlashlightController f = mock(FlashlightController.class);
- mDependency.injectTestDependency(FlashlightController.class, f);
- Assert.assertEquals(f, Dependency.get(FlashlightController.class));
+ FakeClass f = new FakeClass();
+ mDependency.injectTestDependency(FakeClass.class, f);
+ Assert.assertEquals(f, Dependency.get(FakeClass.class));
}
@Test
@@ -53,4 +50,8 @@
Dependency dependency = initializer.getSysUIComponent().createDependency();
dependency.start();
}
+
+ private static class FakeClass {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..095c945 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -22,10 +22,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.DisplayManager;
@@ -75,13 +74,13 @@
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
+ @Mock
private AccessibilityButtonTargetsObserver mTargetsObserver;
+ @Mock
private AccessibilityButtonModeObserver mModeObserver;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
private KeyguardUpdateMonitorCallback mKeyguardCallback;
- private int mLastButtonMode;
- private String mLastButtonTargets;
@Mock
private SecureSettings mSecureSettings;
@@ -97,10 +96,14 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
- mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT);
+
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT));
+
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT));
}
@After
@@ -109,13 +112,6 @@
mController.onAccessibilityButtonTargetsChanged("");
mController = null;
}
-
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastButtonTargets,
- UserHandle.USER_CURRENT);
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mLastButtonMode,
- UserHandle.USER_CURRENT);
}
@Test
@@ -227,9 +223,8 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -239,8 +234,8 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
+
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -250,9 +245,8 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -262,8 +256,7 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -273,9 +266,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -285,9 +277,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -297,9 +288,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -309,9 +299,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -321,9 +310,8 @@
@Test
public void onTargetsChanged_isFloatingViewLayerControllerCreated() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -335,8 +323,6 @@
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
- mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
@@ -348,12 +334,11 @@
}
private void enableAccessibilityFloatingMenuConfig() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
+
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
}
private void captureKeyguardUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 915522d..1a6da76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
},
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index d2a8444..45b2a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var userTracker: UserTracker
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
featureFlags = featureFlags,
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index c65a9ef..92aad69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -703,6 +703,32 @@
coroutineContext.cancelChildren()
}
+ /** This handles security method NONE and screen off with lock timeout */
+ @Test
+ fun dreamingToGoneWithKeyguardNotShowing() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreamingWithOverlay(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+ runCurrent()
+
+ // WHEN the device wakes up without a keyguard
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardRepository.setKeyguardDismissible(true)
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DREAMING,
+ ownerName = "FromDreamingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
@Test
fun dozingToGlanceableHub() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 2ec2fe3..7290863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -218,6 +219,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 1f14afa..bcec6109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -280,6 +280,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -643,7 +644,7 @@
val testConfig =
TestConfig(
- isVisible = true,
+ isVisible = false,
isClickable = false,
icon = mock(),
canShowWhileLocked = false,
@@ -673,7 +674,7 @@
val testConfig =
TestConfig(
- isVisible = true,
+ isVisible = false,
isClickable = false,
icon = mock(),
canShowWhileLocked = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 5dadf21..687f970 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -31,8 +31,11 @@
import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -44,7 +47,8 @@
@SmallTest
class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
+ private val scheduler = TestCoroutineScheduler()
+ private val dispatcher = UnconfinedTestDispatcher(scheduler)
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
@@ -138,6 +142,41 @@
}
@Test
+ fun uiState_taskChanged_beforeDelayLimit_stillEmitsShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION - 1.milliseconds)
+ assertThat(uiState)
+ .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+ }
+
+ @Test
+ fun uiState_taskChanged_afterDelayLimit_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION)
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
fun uiState_projectingTask_foregroundTaskChanged_thenTaskSwitched_emitsNotShowing() =
testScope.runTest {
val projectedTask = createTask(taskId = 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 52859cd..d405df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -43,13 +43,11 @@
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
@@ -61,7 +59,9 @@
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -76,7 +76,6 @@
/** atest NavigationBarControllerTest */
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
@SmallTest
public class NavigationBarControllerImplTest extends SysuiTestCase {
@@ -88,6 +87,8 @@
private StaticMockitoSession mMockitoSession;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
@Mock
private CommandQueue mCommandQueue;
@Mock
@@ -104,7 +105,7 @@
mock(NavigationModeController.class),
mock(SysUiState.class),
mCommandQueue,
- Dependency.get(Dependency.MAIN_HANDLER),
+ mExecutor,
mock(ConfigurationController.class),
mock(NavBarHelper.class),
mTaskbarDelegate,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7b285ab..ada93db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.model.SysUiState
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
@@ -41,18 +42,18 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -74,12 +75,16 @@
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var userFileManager: UserFileManager
@Mock private lateinit var sharedPreferences: SharedPreferences
+ @Mock private lateinit var screenCaptureDisabledDialogDelegate:
+ ScreenCaptureDisabledDialogDelegate
+ @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog
@Mock private lateinit var sysuiState: SysUiState
@Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var bgExecutor: Executor
- @Mock private lateinit var mainExecutor: Executor
+ private val systemClock = FakeSystemClock()
+ private val bgExecutor = FakeExecutor(systemClock)
+ private val mainExecutor = FakeExecutor(systemClock)
@Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
private lateinit var dialog: SystemUIDialog
@@ -92,6 +97,8 @@
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
whenever(userContextProvider.userContext).thenReturn(mContext)
+ whenever(screenCaptureDisabledDialogDelegate.createDialog())
+ .thenReturn(screenCaptureDisabledDialog)
whenever(
userFileManager.getSharedPreferences(
eq(RecordIssueTile.TILE_SPEC),
@@ -124,6 +131,7 @@
dprLazy,
mediaProjectionMetricsLogger,
userFileManager,
+ screenCaptureDisabledDialogDelegate,
) {
latch.countDown()
}
@@ -163,13 +171,8 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
-
- val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mainExecutor).execute(mainCaptor.capture())
- mainCaptor.value.run()
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
verify(mediaProjectionMetricsLogger, never())
.notifyProjectionInitiated(
@@ -192,13 +195,8 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
-
- val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mainExecutor).execute(mainCaptor.capture())
- mainCaptor.value.run()
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
@@ -219,9 +217,7 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
+ bgExecutor.runAllReady()
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6cbe8c9..b3df12ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,13 +17,11 @@
package com.android.systemui.screenrecord;
import static android.os.Process.myUid;
-
import static com.google.common.truth.Truth.assertThat;
-
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -48,10 +46,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -84,8 +81,6 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
- private UserContextProvider mUserContextProvider;
- @Mock
private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
@Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@@ -96,6 +91,22 @@
@Mock
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ @Mock
+ private SystemUIDialog mScreenCaptureDisabledDialog;
+ @Mock
+ private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+ @Mock
+ private ScreenRecordDialogDelegate mScreenRecordDialogDelegate;
+ @Mock
+ private ScreenRecordPermissionDialogDelegate.Factory
+ mScreenRecordPermissionDialogDelegateFactory;
+ @Mock
+ private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate;
+ @Mock
+ private SystemUIDialog mScreenRecordSystemUIDialog;
+
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
private TestSystemUIDialogFactory mDialogFactory;
@@ -108,8 +119,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
-
mDialogFactory = new TestSystemUIDialogFactory(
mContext,
Dependency.get(SystemUIDialogManager.class),
@@ -119,16 +128,26 @@
);
mFeatureFlags = new FakeFeatureFlags();
+ when(mScreenCaptureDisabledDialogDelegate.createDialog())
+ .thenReturn(mScreenCaptureDisabledDialog);
+ when(mScreenRecordDialogFactory.create(any(), any()))
+ .thenReturn(mScreenRecordDialogDelegate);
+ when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog);
+ when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any()))
+ .thenReturn(mScreenRecordPermissionDialogDelegate);
+ when(mScreenRecordPermissionDialogDelegate.createDialog())
+ .thenReturn(mScreenRecordSystemUIDialog);
mController = new RecordingController(
mMainExecutor,
mBroadcastDispatcher,
- mContext,
mFeatureFlags,
- mUserContextProvider,
() -> mDevicePolicyResolver,
mUserTracker,
mMediaProjectionMetricsLogger,
- mDialogFactory);
+ mScreenCaptureDisabledDialogDelegate,
+ mScreenRecordDialogFactory,
+ mScreenRecordPermissionDialogDelegateFactory
+ );
mController.addCallback(mCallback);
}
@@ -242,8 +261,8 @@
mActivityStarter,
/* onStartRecordingClicked= */ null);
- assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
- assertThat(mDialogFactory.mLastDelegate)
+ assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+ assertThat(mScreenRecordPermissionDialogDelegate)
.isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
}
@@ -255,7 +274,7 @@
Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
- assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+ assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
}
@Test
@@ -267,7 +286,7 @@
Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
- assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+ assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog);
}
@Test
@@ -284,8 +303,8 @@
mActivityStarter,
/* onStartRecordingClicked= */ null);
- assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
- assertThat(mDialogFactory.mLastDelegate)
+ assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+ assertThat(mScreenRecordPermissionDialogDelegate)
.isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 90ced92..6e48074 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -58,6 +59,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
+ //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
@Mock private lateinit var starter: ActivityStarter
@Mock private lateinit var controller: RecordingController
@Mock private lateinit var userContextProvider: UserContextProvider
@@ -71,14 +73,17 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+
val systemUIDialogFactory =
- SystemUIDialog.Factory(
- context,
- Dependency.get(SystemUIDialogManager::class.java),
- Dependency.get(SysUiState::class.java),
- Dependency.get(BroadcastDispatcher::class.java),
- Dependency.get(DialogTransitionAnimator::class.java),
- )
+ SystemUIDialog.Factory(
+ context,
+ Dependency.get(SystemUIDialogManager::class.java),
+ Dependency.get(SysUiState::class.java),
+ Dependency.get(BroadcastDispatcher::class.java),
+ Dependency.get(DialogTransitionAnimator::class.java),
+ )
+
val delegate =
ScreenRecordPermissionDialogDelegate(
UserHandle.of(0),
@@ -88,11 +93,9 @@
userContextProvider,
onStartRecordingClicked,
mediaProjectionMetricsLogger,
- systemUIDialogFactory
+ systemUIDialogFactory,
)
dialog = delegate.createDialog()
- delegate.onCreate(dialog, savedInstanceState = null)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index fd7b139..acbf997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -287,7 +287,7 @@
@Mock protected KeyguardMediaController mKeyguardMediaController;
@Mock protected NavigationModeController mNavigationModeController;
@Mock protected NavigationBarController mNavigationBarController;
- @Mock protected QuickSettingsController mQsController;
+ @Mock protected QuickSettingsControllerImpl mQsController;
@Mock protected ShadeHeaderController mShadeHeaderController;
@Mock protected ContentResolver mContentResolver;
@Mock protected TapAgainViewController mTapAgainViewController;
@@ -380,7 +380,7 @@
protected final ShadeExpansionStateManager mShadeExpansionStateManager =
new ShadeExpansionStateManager();
- protected QuickSettingsController mQuickSettingsController;
+ protected QuickSettingsControllerImpl mQuickSettingsController;
@Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
protected FragmentHostManager.FragmentListener mFragmentListener;
@@ -794,7 +794,7 @@
when(mNotificationPanelViewControllerLazy.get())
.thenReturn(mNotificationPanelViewController);
- mQuickSettingsController = new QuickSettingsController(
+ mQuickSettingsController = new QuickSettingsControllerImpl(
mNotificationPanelViewControllerLazy,
mView,
mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 960fd59..617b25d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -112,7 +112,7 @@
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
- @Mock private lateinit var quickSettingsController: QuickSettingsController
+ @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var lockIconViewController: LockIconViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 0b49a95..4809a47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -109,7 +109,7 @@
import kotlinx.coroutines.test.TestScope;
-public class QuickSettingsControllerBaseTest extends SysuiTestCase {
+public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
protected static final float QS_FRAME_START_X = 0f;
protected static final int QS_FRAME_WIDTH = 1000;
protected static final int QS_FRAME_TOP = 0;
@@ -119,7 +119,7 @@
protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
protected static final int DEFAULT_MIN_HEIGHT = 300;
- protected QuickSettingsController mQsController;
+ protected QuickSettingsControllerImpl mQsController;
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected TestScope mTestScope = mKosmos.getTestScope();
@@ -304,7 +304,7 @@
mMainHandler = new Handler(Looper.getMainLooper());
- mQsController = new QuickSettingsController(
+ mQsController = new QuickSettingsControllerImpl(
mPanelViewControllerLazy,
mPanelView,
mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 997e0e2..b16f412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -41,8 +41,8 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,7 +53,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest {
+public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest {
@Test
public void testCloseQsSideEffects() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index cc4a063..2c453a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -27,7 +27,7 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() {
+class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImplBaseTest() {
@Test
fun isExpansionEnabled_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 1dafcc4..b0404a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -15,13 +15,14 @@
package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java
deleted file mode 100644
index 01905bb..0000000
--- a/services/companion/java/com/android/server/companion/AssociationStore.java
+++ /dev/null
@@ -1,144 +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.server.companion;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Interface for a store of {@link AssociationInfo}-s.
- */
-public interface AssociationStore {
-
- @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
- CHANGE_TYPE_ADDED,
- CHANGE_TYPE_REMOVED,
- CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
- CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ChangeType {}
-
- int CHANGE_TYPE_ADDED = 0;
- int CHANGE_TYPE_REMOVED = 1;
- int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
- int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
-
- /** Listener for any changes to {@link AssociationInfo}-s. */
- interface OnChangeListener {
- default void onAssociationChanged(
- @ChangeType int changeType, AssociationInfo association) {
- switch (changeType) {
- case CHANGE_TYPE_ADDED:
- onAssociationAdded(association);
- break;
-
- case CHANGE_TYPE_REMOVED:
- onAssociationRemoved(association);
- break;
-
- case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
- onAssociationUpdated(association, true);
- break;
-
- case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
- onAssociationUpdated(association, false);
- break;
- }
- }
-
- default void onAssociationAdded(AssociationInfo association) {}
-
- default void onAssociationRemoved(AssociationInfo association) {}
-
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
- }
-
- /**
- * @return all CDM associations.
- */
- @NonNull
- Collection<AssociationInfo> getAssociations();
-
- /**
- * @return a {@link List} of associations that belong to the user.
- */
- @NonNull
- List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId);
-
- /**
- * @return a {@link List} of association that belong to the package.
- */
- @NonNull
- List<AssociationInfo> getAssociationsForPackage(
- @UserIdInt int userId, @NonNull String packageName);
-
- /**
- * @return an association with the given address that belong to the given package if such an
- * association exists, otherwise {@code null}.
- */
- @Nullable
- AssociationInfo getAssociationsForPackageWithAddress(
- @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress);
-
- /**
- * @return an association with the given id if such an association exists, otherwise
- * {@code null}.
- */
- @Nullable
- AssociationInfo getAssociationById(int id);
-
- /**
- * @return all associations with the given MAc address.
- */
- @NonNull
- List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress);
-
- /** Register a {@link OnChangeListener} */
- void registerListener(@NonNull OnChangeListener listener);
-
- /** Un-register a previously registered {@link OnChangeListener} */
- void unregisterListener(@NonNull OnChangeListener listener);
-
- /** @hide */
- static String changeTypeToString(@ChangeType int changeType) {
- switch (changeType) {
- case CHANGE_TYPE_ADDED:
- return "ASSOCIATION_ADDED";
-
- case CHANGE_TYPE_REMOVED:
- return "ASSOCIATION_REMOVED";
-
- case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
- return "ASSOCIATION_UPDATED";
-
- case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
- return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED";
-
- default:
- return "Unknown (" + changeType + ")";
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index e4cc1f8..f2409fb 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -34,6 +34,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import java.nio.ByteBuffer;
@@ -54,9 +57,9 @@
@NonNull
private final PackageManagerInternal mPackageManager;
@NonNull
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
@NonNull
- private final PersistentDataStore mPersistentStore;
+ private final AssociationDiskStore mPersistentStore;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
@@ -71,8 +74,8 @@
new PerUserAssociationSet();
BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore,
- @NonNull PersistentDataStore persistentStore,
+ @NonNull AssociationStore associationStore,
+ @NonNull AssociationDiskStore persistentStore,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull AssociationRequestsProcessor associationRequestsProcessor) {
mService = service;
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 559ebbc..c801489 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -37,6 +37,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.PerUser;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 17ba073..e4a1048 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -37,7 +37,9 @@
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
@@ -117,6 +119,11 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.InactiveAssociationsRemovalService;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
@@ -147,8 +154,6 @@
static final String TAG = "CDM_CompanionDeviceManagerService";
static final boolean DEBUG = false;
- /** Range of Association IDs allocated for a user. */
- private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
@@ -160,10 +165,10 @@
private static final int MAX_CN_LENGTH = 500;
private final ActivityManager mActivityManager;
- private PersistentDataStore mPersistentStore;
+ private AssociationDiskStore mAssociationDiskStore;
private final PersistUserStateHandler mUserPersistenceHandler;
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private AssociationRequestsProcessor mAssociationRequestsProcessor;
private SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -178,7 +183,7 @@
private final IAppOpsService mAppOpsManager;
private final PowerWhitelistManager mPowerWhitelistManager;
private final UserManager mUserManager;
- final PackageManagerInternal mPackageManagerInternal;
+ public final PackageManagerInternal mPackageManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
/**
@@ -210,7 +215,7 @@
mUserManager = context.getSystemService(UserManager.class);
mUserPersistenceHandler = new PersistUserStateHandler();
- mAssociationStore = new AssociationStoreImpl();
+ mAssociationStore = new AssociationStore();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -221,11 +226,11 @@
public void onStart() {
final Context context = getContext();
- mPersistentStore = new PersistentDataStore();
+ mAssociationDiskStore = new AssociationDiskStore();
mAssociationRequestsProcessor = new AssociationRequestsProcessor(
/* cdmService */ this, mAssociationStore);
mBackupRestoreProcessor = new BackupRestoreProcessor(
- /* cdmService */ this, mAssociationStore, mPersistentStore,
+ /* cdmService */ this, mAssociationStore, mAssociationDiskStore,
mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -264,10 +269,13 @@
void loadAssociationsFromDisk() {
final Set<AssociationInfo> allAssociations = new ArraySet<>();
synchronized (mPreviouslyUsedIds) {
+ List<Integer> userIds = new ArrayList<>();
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ userIds.add(user.id);
+ }
// The data is stored in DE directories, so we can read the data for all users now
// (which would not be possible if the data was stored to CE directories).
- mPersistentStore.readStateForUsers(
- mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
+ mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds);
}
final Set<AssociationInfo> activeAssociations =
@@ -291,7 +299,7 @@
}
}
- mAssociationStore.setAssociations(activeAssociations);
+ mAssociationStore.setAssociationsToCache(activeAssociations);
// IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
// persistStateForUser() queries AssociationStore.
@@ -582,7 +590,7 @@
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
- mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
+ mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -646,7 +654,8 @@
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsForPackage(userId, packageName);
for (AssociationInfo association : associationsForPackage) {
- updateSpecialAccessPermissionForAssociatedPackage(association);
+ updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
}
mCompanionAppController.onPackagesChanged(userId);
@@ -692,7 +701,7 @@
}
}
- class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+ public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -1338,7 +1347,10 @@
return usedIdsForPackage;
}
- int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ /**
+ * Get a new association id for the package.
+ */
+ public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
synchronized (mPreviouslyUsedIds) {
// First: collect all IDs currently in use for this user's Associations.
final SparseBooleanArray usedIds = new SparseBooleanArray();
@@ -1383,9 +1395,12 @@
}
}
- void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
+ /**
+ * Update special access for the association's package
+ */
+ public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
final PackageInfo packageInfo =
- getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
+ getPackageInfo(getContext(), userId, packageName);
Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
}
@@ -1539,15 +1554,6 @@
}
};
- static int getFirstAssociationIdForUser(@UserIdInt int userId) {
- // We want the IDs to start from 1, not 0.
- return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
- }
-
- static int getLastAssociationIdForUser(@UserIdInt int userId) {
- return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
- }
-
private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
final Map<String, Set<Integer>> copy = new HashMap<>();
@@ -1671,11 +1677,17 @@
}
}
- void postPersistUserState(@UserIdInt int userId) {
+ /**
+ * Persist associations
+ */
+ public void postPersistUserState(@UserIdInt int userId) {
mUserPersistenceHandler.postPersistUserState(userId);
}
- static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ /**
+ * Set to store associations
+ */
+ public static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
@Override
protected @NonNull Set<AssociationInfo> create(int userId) {
return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 74b4cab..16877dc 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -32,6 +32,9 @@
import android.util.Base64;
import android.util.proto.ProtoOutputStream;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
@@ -47,7 +50,7 @@
private final CompanionDeviceManagerService mService;
private final AssociationRevokeProcessor mRevokeProcessor;
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
@@ -56,7 +59,7 @@
private final BackupRestoreProcessor mBackupRestoreProcessor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
- AssociationStoreImpl associationStore,
+ AssociationStore associationStore,
CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
similarity index 92%
rename from services/companion/java/com/android/server/companion/PersistentDataStore.java
rename to services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 7527efb..75cb120 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -25,8 +25,8 @@
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
-import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
@@ -38,12 +38,10 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
-import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.AtomicFile;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -51,7 +49,6 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.companion.utils.DataStoreUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -71,6 +68,8 @@
import java.util.concurrent.ConcurrentMap;
/**
+ * IMPORTANT: This class should NOT be directly used except {@link AssociationStore}
+ *
* The class responsible for persisting Association records and other related information (such as
* previously used IDs) to a disk, and reading the data back from the disk.
*
@@ -107,8 +106,6 @@
* Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
- * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
- *
* <p>
* Since Android T the data is stored using the v1 schema.
*
@@ -161,9 +158,8 @@
* }</pre>
*/
@SuppressLint("LongLogTag")
-final class PersistentDataStore {
- private static final String TAG = "CompanionDevice_PersistentDataStore";
- private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
+public final class AssociationDiskStore {
+ private static final String TAG = "CompanionDevice_AssociationDiskStore";
private static final int CURRENT_PERSISTENCE_VERSION = 1;
@@ -200,11 +196,13 @@
private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
new ConcurrentHashMap<>();
- void readStateForUsers(@NonNull List<UserInfo> users,
+ /**
+ * Read all associations for given users
+ */
+ public void readStateForUsers(@NonNull List<Integer> userIds,
@NonNull Set<AssociationInfo> allAssociationsOut,
@NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
- for (UserInfo user : users) {
- final int userId = user.id;
+ for (int userId : userIds) {
// Previously used IDs are stored in the "out" collection per-user.
final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
@@ -247,12 +245,11 @@
* @param associationsOut a container to read the {@link AssociationInfo}s "into".
* @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
*/
- void readStateForUser(@UserIdInt int userId,
+ private void readStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
Slog.i(TAG, "Reading associations for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
@@ -261,12 +258,8 @@
final AtomicFile readFrom;
final String rootTag;
if (!file.getBaseFile().exists()) {
- if (DEBUG) Log.d(TAG, " > File does not exist -> Try to read legacy file");
-
legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > Legacy file=" + legacyBaseFile.getPath());
if (!legacyBaseFile.exists()) {
- if (DEBUG) Log.d(TAG, " > Legacy file does not exist -> Abort");
return;
}
@@ -277,27 +270,16 @@
rootTag = XML_TAG_STATE;
}
- if (DEBUG) Log.d(TAG, " > Reading associations...");
final int version = readStateFromFileLocked(userId, readFrom, rootTag,
associationsOut, previouslyUsedIdsPerPackageOut);
- if (DEBUG) {
- Log.d(TAG, " > Done reading: " + associationsOut);
- if (version < CURRENT_PERSISTENCE_VERSION) {
- Log.d(TAG, " > File used old format: v." + version + " -> Re-write");
- }
- }
if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
// The data is either in the legacy file or in the legacy format, or both.
// Save the data to right file in using the current format.
- if (DEBUG) {
- Log.d(TAG, " > Writing the data to " + file.getBaseFile().getPath());
- }
persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
if (legacyBaseFile != null) {
// We saved the data to the right file, can delete the old file now.
- if (DEBUG) Log.d(TAG, " > Deleting legacy file");
legacyBaseFile.delete();
}
}
@@ -314,14 +296,12 @@
* @param associations a set of user's associations.
* @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
*/
- void persistStateForUser(@UserIdInt int userId,
+ public void persistStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associations,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
Slog.i(TAG, "Writing associations for user " + userId + " to disk");
- if (DEBUG) Slog.d(TAG, " > " + associations);
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
synchronized (file) {
@@ -404,7 +384,10 @@
u -> createStorageFileForUser(userId, FILE_NAME));
}
- byte[] getBackupPayload(@UserIdInt int userId) {
+ /**
+ * Get associations backup payload from disk
+ */
+ public byte[] getBackupPayload(@UserIdInt int userId) {
Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
@@ -413,7 +396,10 @@
}
}
- void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+ /**
+ * Convert payload to a set of associations
+ */
+ public void readStateFromPayload(byte[] payload, @UserIdInt int userId,
@NonNull Set<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
@@ -615,7 +601,7 @@
macAddress, displayName, profile, null, selfManaged, notify,
revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
} catch (Exception e) {
- if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
+ Slog.e(TAG, "Could not create AssociationInfo", e);
}
return associationInfo;
}
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
similarity index 89%
rename from services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 1dab40e..29ec7c2 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -24,7 +24,6 @@
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
@@ -59,6 +58,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.utils.PackageUtils;
import java.util.List;
@@ -107,7 +107,7 @@
* ResultReceiver, MacAddress)
*/
@SuppressLint("LongLogTag")
-class AssociationRequestsProcessor {
+public class AssociationRequestsProcessor {
private static final String TAG = "CDM_AssociationRequestsProcessor";
// AssociationRequestsProcessor <-> UI
@@ -130,12 +130,12 @@
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
private final @NonNull PackageManagerInternal mPackageManager;
- private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull AssociationStore mAssociationStore;
@NonNull
private final ComponentName mCompanionDeviceActivity;
- AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore) {
+ public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore) {
mContext = service.getContext();
mService = service;
mPackageManager = service.mPackageManagerInternal;
@@ -149,7 +149,7 @@
* Handle incoming {@link AssociationRequest}s, sent via
* {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
*/
- void processNewAssociationRequest(@NonNull AssociationRequest request,
+ public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@NonNull IAssociationRequestCallback callback) {
requireNonNull(request, "Request MUST NOT be null");
@@ -161,11 +161,8 @@
requireNonNull(callback, "Callback MUST NOT be null");
final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
- if (DEBUG) {
- Slog.d(TAG, "processNewAssociationRequest() "
- + "request=" + request + ", "
- + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")");
- }
+ Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u"
+ + userId + "/" + packageName + " (uid=" + packageUid + ")");
// 1. Enforce permissions and other requirements.
enforcePermissionForCreatingAssociation(mContext, request, packageUid);
@@ -223,7 +220,7 @@
/**
* Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog.
*/
- PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
+ public PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
@UserIdInt int userId) {
requireNonNull(packageName, "Package name MUST NOT be null");
@@ -248,13 +245,6 @@
final int userId = request.getUserId();
final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
- if (DEBUG) {
- Slog.d(TAG, "processAssociationRequestApproval()\n"
- + " package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n"
- + " request=" + request + "\n"
- + " macAddress=" + macAddress + "\n");
- }
-
// 1. Need to check permissions again in case something changed, since we first received
// this request.
try {
@@ -288,6 +278,9 @@
}
}
+ /**
+ * Create an association.
+ */
public void createAssociation(@UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
@@ -309,6 +302,9 @@
// that there are other devices with the same profile, so the role holder won't be removed.
}
+ /**
+ * Grant a role if specified and add an association to store.
+ */
public void maybeGrantRoleAndStoreAssociation(@NonNull AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -331,6 +327,9 @@
});
}
+ /**
+ * Enable system data sync.
+ */
public void enableSystemDataSync(int associationId, int flags) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -338,6 +337,9 @@
mAssociationStore.updateAssociation(updated);
}
+ /**
+ * Disable system data sync.
+ */
public void disableSystemDataSync(int associationId, int flags) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -350,7 +352,8 @@
mAssociationStore.addAssociation(association);
- mService.updateSpecialAccessPermissionForAssociatedPackage(association);
+ mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
logCreateAssociation(association.getDeviceProfile());
}
@@ -431,38 +434,37 @@
private final ResultReceiver mOnRequestConfirmationReceiver =
new ResultReceiver(Handler.getMain()) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle data) {
- if (DEBUG) {
- Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() "
- + "code=" + resultCode + ", " + "data=" + data);
- }
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle data) {
+ if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
+ Slog.w(TAG, "Unknown result code:" + resultCode);
+ return;
+ }
- if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
- Slog.w(TAG, "Unknown result code:" + resultCode);
- return;
- }
+ final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST,
+ android.companion.AssociationRequest.class);
+ final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
+ .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
+ final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER,
+ android.os.ResultReceiver.class);
- final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST, android.companion.AssociationRequest.class);
- final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
- .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
- final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER, android.os.ResultReceiver.class);
+ requireNonNull(request);
+ requireNonNull(callback);
+ requireNonNull(resultReceiver);
- requireNonNull(request);
- requireNonNull(callback);
- requireNonNull(resultReceiver);
+ final MacAddress macAddress;
+ if (request.isSelfManaged()) {
+ macAddress = null;
+ } else {
+ macAddress = data.getParcelable(EXTRA_MAC_ADDRESS,
+ android.net.MacAddress.class);
+ requireNonNull(macAddress);
+ }
- final MacAddress macAddress;
- if (request.isSelfManaged()) {
- macAddress = null;
- } else {
- macAddress = data.getParcelable(EXTRA_MAC_ADDRESS, android.net.MacAddress.class);
- requireNonNull(macAddress);
- }
-
- processAssociationRequestApproval(request, callback, resultReceiver, macAddress);
- }
- };
+ processAssociationRequestApproval(request, callback, resultReceiver,
+ macAddress);
+ }
+ };
private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) {
// Throttle frequent associations
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
similarity index 95%
rename from services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
index 10963ea..490be0d 100644
--- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -38,6 +38,8 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.CompanionApplicationController;
+import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -55,7 +57,7 @@
private static final boolean DEBUG = false;
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
- private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull AssociationStore mAssociationStore;
private final @NonNull PackageManagerInternal mPackageManagerInternal;
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@@ -90,8 +92,8 @@
@GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
- AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore,
+ public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
@NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
@NonNull CompanionApplicationController applicationController,
@@ -108,8 +110,11 @@
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
}
+ /**
+ * Disassociate an association
+ */
// TODO: also revoke notification access
- void disassociateInternal(int associationId) {
+ public void disassociateInternal(int associationId) {
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -168,7 +173,7 @@
* {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
* which would lead to the poor UX, hence need to try later.
*/
- boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
final String deviceProfile = association.getDeviceProfile();
@@ -208,15 +213,6 @@
return true;
}
- @SuppressLint("MissingPermission")
- private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
- return Binder.withCleanCallingIdentity(() -> {
- final int uid =
- mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
- return mActivityManager.getUidImportance(uid);
- });
- }
-
/**
* Set revoked flag for active association and add the revoked association and the uid into
* the caches.
@@ -225,7 +221,7 @@
* @see #mUidsPendingRoleHolderRemoval
* @see OnPackageVisibilityChangeListener
*/
- void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
// First: set revoked flag
association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
final String packageName = association.getPackageName();
@@ -247,6 +243,28 @@
}
/**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ @NonNull
+ public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
* Remove the revoked association from the cache and also remove the uid from the map if
* there are other associations with the same package still pending for role holder removal.
*
@@ -279,18 +297,6 @@
}
}
- /**
- * @return a copy of the revoked associations set (safeguarding against
- * {@code ConcurrentModificationException}-s).
- */
- @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
- @UserIdInt int userId) {
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- // Return a copy.
- return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
- }
- }
-
private String getPackageNameByUid(int uid) {
synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
return mUidsPendingRoleHolderRemoval.get(uid);
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
similarity index 68%
rename from services/companion/java/com/android/server/companion/AssociationStoreImpl.java
rename to services/companion/java/com/android/server/companion/association/AssociationStore.java
index 8c6ad3b..2f94bde 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.net.MacAddress;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -30,6 +30,8 @@
import com.android.internal.util.CollectionUtils;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -40,24 +42,69 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.StringJoiner;
/**
- * Implementation of the {@link AssociationStore}, with addition of the methods for modification.
- * <ul>
- * <li> {@link #addAssociation(AssociationInfo)}
- * <li> {@link #removeAssociation(int)}
- * <li> {@link #updateAssociation(AssociationInfo)}
- * </ul>
- *
- * The class has package-private access level, and instances of the class should only be created by
- * the {@link CompanionDeviceManagerService}.
- * Other system component (both inside and outside if the com.android.server.companion package)
- * should use public {@link AssociationStore} interface.
+ * Association store for CRUD.
*/
@SuppressLint("LongLogTag")
-class AssociationStoreImpl implements AssociationStore {
- private static final boolean DEBUG = false;
+public class AssociationStore {
+
+ @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+ CHANGE_TYPE_ADDED,
+ CHANGE_TYPE_REMOVED,
+ CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
+ CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ChangeType {}
+
+ public static final int CHANGE_TYPE_ADDED = 0;
+ public static final int CHANGE_TYPE_REMOVED = 1;
+ public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
+ public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
+
+ /** Listener for any changes to associations. */
+ public interface OnChangeListener {
+ /**
+ * Called when there are association changes.
+ */
+ default void onAssociationChanged(
+ @AssociationStore.ChangeType int changeType, AssociationInfo association) {
+ switch (changeType) {
+ case CHANGE_TYPE_ADDED:
+ onAssociationAdded(association);
+ break;
+
+ case CHANGE_TYPE_REMOVED:
+ onAssociationRemoved(association);
+ break;
+
+ case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+ onAssociationUpdated(association, true);
+ break;
+
+ case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+ onAssociationUpdated(association, false);
+ break;
+ }
+ }
+
+ /**
+ * Called when an association is added.
+ */
+ default void onAssociationAdded(AssociationInfo association) {}
+
+ /**
+ * Called when an association is removed.
+ */
+ default void onAssociationRemoved(AssociationInfo association) {}
+
+ /**
+ * Called when an association is updated.
+ */
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+ }
+
private static final String TAG = "CDM_AssociationStore";
private final Object mLock = new Object();
@@ -72,17 +119,17 @@
@GuardedBy("mListeners")
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
- void addAssociation(@NonNull AssociationInfo association) {
+ /**
+ * Add an association.
+ */
+ public void addAssociation(@NonNull AssociationInfo association) {
+ Slog.i(TAG, "Adding new association=" + association);
+
// Validity check first.
checkNotRevoked(association);
final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "addAssociation() " + association.toShortString());
- Log.d(TAG, " association=" + association);
- }
-
synchronized (mLock) {
if (mIdMap.containsKey(id)) {
Slog.e(TAG, "Association with id " + id + " already exists.");
@@ -96,34 +143,34 @@
}
invalidateCacheForUserLocked(association.getUserId());
+
+ Slog.i(TAG, "Done adding new association.");
}
broadcastChange(CHANGE_TYPE_ADDED, association);
}
- void updateAssociation(@NonNull AssociationInfo updated) {
+ /**
+ * Update an association.
+ */
+ public void updateAssociation(@NonNull AssociationInfo updated) {
+ Slog.i(TAG, "Updating new association=" + updated);
// Validity check first.
checkNotRevoked(updated);
final int id = updated.getId();
- if (DEBUG) {
- Log.i(TAG, "updateAssociation() " + updated.toShortString());
- Log.d(TAG, " updated=" + updated);
- }
-
final AssociationInfo current;
final boolean macAddressChanged;
synchronized (mLock) {
current = mIdMap.get(id);
if (current == null) {
- if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist.");
+ Slog.w(TAG, "Can't update association. It does not exist.");
return;
}
- if (DEBUG) Log.d(TAG, " current=" + current);
if (current.equals(updated)) {
- if (DEBUG) Log.w(TAG, " No changes.");
+ Slog.w(TAG, "Association is the same.");
return;
}
@@ -144,6 +191,7 @@
mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
}
}
+ Slog.i(TAG, "Done updating association.");
}
final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
@@ -151,21 +199,19 @@
broadcastChange(changeType, updated);
}
- void removeAssociation(int id) {
- if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id);
+ /**
+ * Remove an association
+ */
+ public void removeAssociation(int id) {
+ Slog.i(TAG, "Removing association id=" + id);
final AssociationInfo association;
synchronized (mLock) {
association = mIdMap.remove(id);
if (association == null) {
- if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored.");
+ Slog.w(TAG, "Can't remove association. It does not exist.");
return;
- } else {
- if (DEBUG) {
- Log.i(TAG, "removed " + association.toShortString());
- Log.d(TAG, " association=" + association);
- }
}
final MacAddress macAddress = association.getDeviceMacAddress();
@@ -174,6 +220,8 @@
}
invalidateCacheForUserLocked(association.getUserId());
+
+ Slog.i(TAG, "Done removing association.");
}
broadcastChange(CHANGE_TYPE_REMOVED, association);
@@ -195,12 +243,18 @@
}
}
+ /**
+ * Get associations for the user.
+ */
public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
synchronized (mLock) {
return getAssociationsForUserLocked(userId);
}
}
+ /**
+ * Get associations for the package
+ */
public @NonNull List<AssociationInfo> getAssociationsForPackage(
@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
@@ -210,6 +264,9 @@
return Collections.unmodifiableList(associationsForPackage);
}
+ /**
+ * Get associations by mac address for the package.
+ */
public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
@@ -217,13 +274,20 @@
it -> it.belongsToPackage(userId, packageName));
}
+ /**
+ * Get association by id.
+ */
public @Nullable AssociationInfo getAssociationById(int id) {
synchronized (mLock) {
return mIdMap.get(id);
}
}
- public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
+ /**
+ * Get associations by mac address.
+ */
+ @NonNull
+ public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
final MacAddress address = MacAddress.fromString(macAddress);
synchronized (mLock) {
@@ -240,7 +304,8 @@
}
@GuardedBy("mLock")
- private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
+ @NonNull
+ private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
final List<AssociationInfo> cached = mCachedPerUser.get(userId);
if (cached != null) {
return cached;
@@ -262,12 +327,18 @@
mCachedPerUser.delete(userId);
}
+ /**
+ * Register a listener for association changes.
+ */
public void registerListener(@NonNull OnChangeListener listener) {
synchronized (mListeners) {
mListeners.add(listener);
}
}
+ /**
+ * Unregister a listener previously registered for association changes.
+ */
public void unregisterListener(@NonNull OnChangeListener listener) {
synchronized (mListeners) {
mListeners.remove(listener);
@@ -297,43 +368,30 @@
}
}
- void setAssociations(Collection<AssociationInfo> allAssociations) {
+ /**
+ * Set associations to cache. It will clear the existing cache.
+ */
+ public void setAssociationsToCache(Collection<AssociationInfo> associations) {
// Validity check first.
- allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+ associations.forEach(AssociationStore::checkNotRevoked);
- if (DEBUG) {
- Log.i(TAG, "setAssociations() n=" + allAssociations.size());
- final StringJoiner stringJoiner = new StringJoiner(", ");
- allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString()));
- Log.v(TAG, " associations=" + stringJoiner);
- }
synchronized (mLock) {
- setAssociationsLocked(allAssociations);
- }
- }
+ mIdMap.clear();
+ mAddressMap.clear();
+ mCachedPerUser.clear();
- @GuardedBy("mLock")
- private void setAssociationsLocked(Collection<AssociationInfo> associations) {
- clearLocked();
+ for (AssociationInfo association : associations) {
+ final int id = association.getId();
+ mIdMap.put(id, association);
- for (AssociationInfo association : associations) {
- final int id = association.getId();
- mIdMap.put(id, association);
-
- final MacAddress address = association.getDeviceMacAddress();
- if (address != null) {
- mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ final MacAddress address = association.getDeviceMacAddress();
+ if (address != null) {
+ mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ }
}
}
}
- @GuardedBy("mLock")
- private void clearLocked() {
- mIdMap.clear();
- mAddressMap.clear();
- mCachedPerUser.clear();
- }
-
private static void checkNotRevoked(@NonNull AssociationInfo association) {
if (association.isRevoked()) {
throw new IllegalArgumentException(
diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
similarity index 84%
rename from services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
rename to services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index aac628c..894c49a 100644
--- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
-
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
+package com.android.server.companion.association;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -29,13 +27,17 @@
import android.util.Slog;
import com.android.server.LocalServices;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
/**
- * A Job Service responsible for clean up the Association.
+ * A Job Service responsible for clean up idle self-managed associations.
+ *
* The job will be executed only if the device is charging and in idle mode due to the application
- * will be killed if association/role are revoked.
+ * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor}
*/
public class InactiveAssociationsRemovalService extends JobService {
+
+ private static final String TAG = "CDM_InactiveAssociationsRemovalService";
private static final String JOB_NAMESPACE = "companion";
private static final int JOB_ID = 1;
private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
@@ -60,7 +62,10 @@
return false;
}
- static void schedule(Context context) {
+ /**
+ * Schedule this job.
+ */
+ public static void schedule(Context context) {
Slog.i(TAG, "Scheduling the Association Removal job");
final JobScheduler jobScheduler =
context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 74236a4..a08e0da 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -52,8 +52,8 @@
import android.util.Slog;
import com.android.internal.R;
-import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
import com.android.server.companion.utils.PermissionsUtils;
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 2899c05..99466a9 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -59,8 +59,8 @@
import android.util.Log;
import android.util.Slog;
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.AssociationStore.ChangeType;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.AssociationStore.ChangeType;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 0287f62..4da3f9b 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -39,7 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.util.Arrays;
import java.util.Collections;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 3da9693..37bbb93 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -44,7 +44,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
import java.util.HashSet;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3861f99..6dd14ac 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -32,7 +32,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/services/companion/java/com/android/server/companion/utils/AssociationUtils.java b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
new file mode 100644
index 0000000..e4d9641
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.utils;
+
+import android.annotation.UserIdInt;
+
+public final class AssociationUtils {
+
+ /** Range of Association IDs allocated for a user. */
+ private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
+
+ /**
+ * Get the left boundary of the association id range for the user.
+ */
+ public static int getFirstAssociationIdForUser(@UserIdInt int userId) {
+ // We want the IDs to start from 1, not 0.
+ return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
+ }
+
+ /**
+ * Get the right boundary of the association id range for the user.
+ */
+ public static int getLastAssociationIdForUser(@UserIdInt int userId) {
+ return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
+ }
+
+ private AssociationUtils() {}
+}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index e1d7be1..1a3ef73 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -264,8 +264,8 @@
final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
// These are the packages that are allow-listed to be able to access camera when
- // the camera privacy state is for driver assistance apps only.
- final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>();
+ // the camera privacy state is enabled.
+ final ArraySet<String> mAllowlistCameraPrivacy = new ArraySet<>();
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
@@ -489,7 +489,7 @@
return mAllowedAssociations;
}
- public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+ public ArraySet<String> getCameraPrivacyAllowlist() {
return mAllowlistCameraPrivacy;
}
@@ -1076,13 +1076,11 @@
case "camera-privacy-allowlisted-app" : {
if (allowOverrideAppRestrictions) {
String pkgname = parser.getAttributeValue(null, "package");
- boolean isMandatory = XmlUtils.readBooleanAttribute(
- parser, "mandatory", false);
if (pkgname == null) {
Slog.w(TAG, "<" + name + "> without package in "
+ permFile + " at " + parser.getPositionDescription());
} else {
- mAllowlistCameraPrivacy.put(pkgname, isMandatory);
+ mAllowlistCameraPrivacy.add(pkgname);
}
} else {
logNotAllowedInPartition(name, permFile, parser);
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d1c8c30..4a37913 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -276,6 +276,11 @@
&& record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
+ if (mLastDeferredStates && shouldBeDeferred()
+ && (record.getDeliveryState(recordIndex)
+ == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
return replacedBroadcastRecord;
}
}
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index e0376ed..8db5905 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -28,5 +28,10 @@
}
]
}
+ ],
+ "postsubmit":[
+ {
+ "name":"FrameworksVpnTests"
+ }
]
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4f86adf..4eb8b2b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -354,6 +354,10 @@
DevicePolicyManager getDevicePolicyManager() {
return mContext.getSystemService(DevicePolicyManager.class);
}
+
+ void setSystemProperty(String key, String value) {
+ SystemProperties.set(key, value);
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -737,7 +741,7 @@
@GuardedBy("mLock")
private IDumpstate startAndGetDumpstateBinderServiceLocked() {
// Start bugreport service.
- SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
+ mInjector.setSystemProperty("ctl.start", BUGREPORT_SERVICE);
IDumpstate ds = null;
boolean timedOut = false;
@@ -769,7 +773,7 @@
// This tells init to cancel bugreportd service. Note that this is achieved through
// setting a system property which is not thread-safe. So the lock here offers
// thread-safety only among callers of the API.
- SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ mInjector.setSystemProperty("ctl.stop", BUGREPORT_SERVICE);
}
@RequiresPermission(android.Manifest.permission.DUMP)
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9afdde5..b5476fd 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -760,13 +760,18 @@
if (pkgName == null) {
if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
resolvedType, userId)) {
- /*
- Check for results in the current profile only if there is no
- {@link CrossProfileIntentFilter} for user with flag
- {@link PackageManager.SKIP_CURRENT_PROFILE} set.
- */
- result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
- intent, resolvedType, flags, userId), userId));
+
+ final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
+ intent, resolvedType, flags, userId);
+ // If the user doesn't exist, the queryResult is null
+ if (queryResult != null) {
+ /*
+ Check for results in the current profile only if there is no
+ {@link CrossProfileIntentFilter} for user with flag
+ {@link PackageManager.SKIP_CURRENT_PROFILE} set.
+ */
+ result.addAll(filterIfNotSystemUser(queryResult, userId));
+ }
}
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
false /*skipPackageCheck*/, flags);
@@ -788,9 +793,13 @@
if (setting != null && setting.getAndroidPackage() != null && (resolveForStart
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
- result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
+ final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
- userId), userId));
+ userId);
+ // If the user doesn't exist, the queryResult is null
+ if (queryResult != null) {
+ result.addAll(filterIfNotSystemUser(queryResult, userId));
+ }
}
if (result == null || result.size() == 0) {
// the caller wants to resolve for a particular package; however, there
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aaf21c8..59d6219 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3386,12 +3386,7 @@
} else if (tagName.equals("verifier")) {
final String deviceIdentity = parser.getAttributeValue(null, "device");
- try {
- mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
- } catch (IllegalArgumentException e) {
- Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
- + e.getMessage());
- }
+ mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
// No longer used.
} else if (tagName.equals("keyset-settings")) {
@@ -3419,7 +3414,8 @@
}
str.close();
- } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException
+ | IllegalArgumentException e) {
// Remove corrupted file and retry.
atomicFile.failRead(str, e);
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index f8c678a..52ef87c 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -45,11 +45,9 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED_EXCEPT_ALLOWLISTED_APPS;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static android.os.UserHandle.USER_NULL;
@@ -57,11 +55,9 @@
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
@@ -98,7 +94,6 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
-import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
import android.hardware.SensorPrivacyManager;
@@ -153,7 +148,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -170,18 +164,12 @@
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
private static final int ACTION__TOGGLE_ON =
PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ private static final int ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
private static final int ACTION__TOGGLE_OFF =
PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@@ -208,8 +196,7 @@
private CallStateHelper mCallStateHelper;
private KeyguardManager mKeyguardManager;
- List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist =
- new ArrayList<CameraPrivacyAllowlistEntry>();
+ List<String> mCameraPrivacyAllowlist = new ArrayList<String>();
private int mCurrentUser = USER_NULL;
@@ -227,14 +214,8 @@
mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
- ArrayMap<String, Boolean> cameraPrivacyAllowlist =
- SystemConfig.getInstance().getCameraPrivacyAllowlist();
-
- for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) {
- CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry();
- ent.packageName = entry.getKey();
- ent.isMandatory = entry.getValue();
- mCameraPrivacyAllowlist.add(ent);
+ for (String entry : SystemConfig.getInstance().getCameraPrivacyAllowlist()) {
+ mCameraPrivacyAllowlist.add(entry);
}
}
@@ -908,14 +889,8 @@
case DISABLED :
logAction = ACTION__TOGGLE_ON;
break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
- break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
- break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+ case ENABLED_EXCEPT_ALLOWLISTED_APPS :
+ logAction = ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
break;
default :
logAction = ACTION__ACTION_UNKNOWN;
@@ -981,11 +956,23 @@
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() {
+ public List<String> getCameraPrivacyAllowlist() {
enforceObserveSensorPrivacyPermission();
return mCameraPrivacyAllowlist;
}
+ /**
+ * Sets camera privacy allowlist.
+ * @param allowlist List of automotive driver assistance packages for
+ * privacy allowlisting.
+ * @hide
+ */
+ @Override
+ public void setCameraPrivacyAllowlist(List<String> allowlist) {
+ enforceManageSensorPrivacyPermission();
+ mCameraPrivacyAllowlist = new ArrayList<>(allowlist);
+ }
+
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@@ -1005,23 +992,9 @@
return true;
} else if (state == DISABLED) {
return false;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if ((packageName.equals(entry.packageName)) && !entry.isMandatory) {
- return false;
- }
- }
- return true;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if ((packageName.equals(entry.packageName)) && entry.isMandatory) {
- return false;
- }
- }
- return true;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if (packageName.equals(entry.packageName)) {
+ } else if (state == ENABLED_EXCEPT_ALLOWLISTED_APPS) {
+ for (String entry : mCameraPrivacyAllowlist) {
+ if (packageName.equals(entry)) {
return false;
}
}
@@ -1616,7 +1589,7 @@
setToggleSensorPrivacy(userId, SHELL, sensor, false);
}
break;
- case "automotive_driver_assistance_apps" : {
+ case "enable_except_allowlisted_apps" : {
if (Flags.cameraPrivacyAllowlist()) {
int sensor = sensorStrToId(getNextArgRequired());
if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
@@ -1625,33 +1598,7 @@
}
setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_APPS);
- }
- }
- break;
- case "automotive_driver_assistance_helpful_apps" : {
- if (Flags.cameraPrivacyAllowlist()) {
- int sensor = sensorStrToId(getNextArgRequired());
- if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
- pw.println("Command not valid for this sensor");
- return -1;
- }
-
- setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS);
- }
- }
- break;
- case "automotive_driver_assistance_required_apps" : {
- if (Flags.cameraPrivacyAllowlist()) {
- int sensor = sensorStrToId(getNextArgRequired());
- if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
- pw.println("Command not valid for this sensor");
- return -1;
- }
-
- setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS);
+ ENABLED_EXCEPT_ALLOWLISTED_APPS);
}
}
break;
@@ -1679,18 +1626,9 @@
pw.println("");
if (Flags.cameraPrivacyAllowlist()) {
if (isAutomotive(mContext)) {
- pw.println(" automotive_driver_assistance_apps USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which help you"
- + " drive and apps which are required by OEM");
- pw.println("");
- pw.println(" automotive_driver_assistance_helpful_apps "
+ pw.println(" enable_except_allowlisted_apps "
+ "USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which "
- + "help you drive.");
- pw.println("");
- pw.println(" automotive_driver_assistance_required_apps "
- + "USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which are "
+ pw.println(" Enable privacy except for automotive apps which are "
+ "required by OEM.");
pw.println("");
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index e9c4096..043470f 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -30,6 +30,7 @@
import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
@@ -37,6 +38,7 @@
import android.webkit.WebViewProviderResponse;
import com.android.internal.util.DumpUtils;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -52,6 +54,14 @@
private static final String TAG = "WebViewUpdateService";
+ private static final Histogram sPrepareWebViewInSystemServerLatency = new Histogram(
+ "webview.value_prepare_webview_in_system_server_latency",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.5f));
+
+ private static final Histogram sAppWaitingForRelroCompletionDelay = new Histogram(
+ "webview.value_app_waiting_for_relro_completion_delay",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private BroadcastReceiver mWebViewUpdatedReceiver;
private WebViewUpdateServiceInterface mImpl;
@@ -132,7 +142,10 @@
}
public void prepareWebViewInSystemServer() {
+ long currentTimeMs = SystemClock.uptimeMillis();
mImpl.prepareWebViewInSystemServer();
+ sPrepareWebViewInSystemServerLatency.logSample(
+ (float) (SystemClock.uptimeMillis() - currentTimeMs));
}
private static String packageNameFromIntent(Intent intent) {
@@ -204,8 +217,12 @@
throw new IllegalStateException("Cannot create a WebView from the SystemServer");
}
+ long startTimeMs = SystemClock.uptimeMillis();
final WebViewProviderResponse webViewProviderResponse =
WebViewUpdateService.this.mImpl.waitForAndGetProvider();
+ long endTimeMs = SystemClock.uptimeMillis();
+ sAppWaitingForRelroCompletionDelay.logSample((float) (endTimeMs - startTimeMs));
+
if (webViewProviderResponse.packageInfo != null) {
grantVisibilityToCaller(
webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 1d6ad6d..532ff98 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -23,12 +23,15 @@
import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
+import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.modules.expresslog.Counter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -357,6 +360,12 @@
mNumRelroCreationsFinished = 0;
mNumRelroCreationsStarted =
mSystemInterface.onWebViewProviderChanged(newPackage);
+ Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+ if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+ Counter.logIncrement(
+ "webview.value_on_webview_provider_changed_"
+ + "with_default_package_counter");
+ }
// If the relro creations finish before we know the number of started creations
// we will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -388,9 +397,15 @@
@Override
public WebViewProviderInfo getDefaultWebViewPackage() {
- throw new IllegalStateException(
- "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is"
- + " disabled.");
+ for (WebViewProviderInfo provider : getWebViewPackages()) {
+ if (provider.availableByDefault) {
+ return provider;
+ }
+ }
+
+ // This should be unreachable because the config parser enforces that there is at least
+ // one availableByDefault provider.
+ throw new AndroidRuntimeException("No available by default WebView Provider.");
}
private static class ProviderAndPackageInfo {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 1f9d265..fb338ba 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -31,6 +31,8 @@
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.modules.expresslog.Counter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -412,6 +414,12 @@
mNumRelroCreationsFinished = 0;
mNumRelroCreationsStarted =
mSystemInterface.onWebViewProviderChanged(newPackage);
+ Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+ if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+ Counter.logIncrement(
+ "webview.value_on_webview_provider_changed_"
+ + "with_default_package_counter");
+ }
// If the relro creations finish before we know the number of started creations
// we will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -479,6 +487,7 @@
* for all users, otherwise use the default provider.
*/
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+ Counter.logIncrement("webview.value_find_preferred_webview_package_counter");
// If the user has chosen provider, use that (if it's installed and enabled for all
// users).
String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
@@ -508,12 +517,15 @@
PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(mDefaultProvider);
if (validityResult(mDefaultProvider, packageInfo) == VALIDITY_OK) {
return packageInfo;
+ } else {
+ Counter.logIncrement("webview.value_default_webview_package_invalid_counter");
}
} catch (NameNotFoundException e) {
Slog.w(TAG, "Default WebView package (" + mDefaultProvider.packageName + ") not found");
}
// This should never happen during normal operation (only with modified system images).
+ Counter.logIncrement("webview.value_webview_not_usable_for_all_users_counter");
mAnyWebViewInstalled = false;
throw new WebViewPackageMissingException("Could not find a loadable WebView package");
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 91eff18..4189988 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1209,6 +1209,7 @@
private boolean mShown;
private boolean mLastSurfaceShown;
private int mAlpha;
+ private int mPreviousAlpha;
private volatile boolean mInvalidated;
@@ -1344,6 +1345,7 @@
// using WindowManagerGlobalLock. Grab copies of these values before
// drawing on the canvas so that drawing can be performed outside of the lock.
int alpha;
+ boolean redrawBounds;
Rect drawingRect = null;
Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
@@ -1361,7 +1363,13 @@
mInvalidated = false;
alpha = mAlpha;
- if (alpha > 0) {
+ // For b/325863281, we should ensure the drawn border path is cleared when
+ // alpha = 0. Therefore, we cache the last used alpha when drawing as
+ // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
+ // the border is showing now, then we should still redraw the clear path
+ // on the canvas so the border is cleared.
+ redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
+ if (redrawBounds) {
drawingBounds = new Region(mBounds);
// Empty dirty rectangle means unspecified.
if (mDirtyRect.isEmpty()) {
@@ -1378,7 +1386,7 @@
final boolean showSurface;
// Draw without holding WindowManagerGlobalLock.
- if (alpha > 0) {
+ if (redrawBounds) {
Canvas canvas = null;
try {
canvas = mSurface.lockCanvas(drawingRect);
@@ -1392,11 +1400,11 @@
mPaint.setAlpha(alpha);
canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
mSurface.unlockCanvasAndPost(canvas);
- showSurface = true;
- } else {
- showSurface = false;
+ mPreviousAlpha = alpha;
}
+ showSurface = alpha > 0;
+
if (showSurface && !mLastSurfaceShown) {
mTransaction.show(mSurfaceControl).apply();
mLastSurfaceShown = true;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index fa76774..b88d7f7 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -311,7 +311,7 @@
return mInsetsHint;
}
final WindowState win = mWindowContainer.asWindowState();
- if (win != null && win.mGivenInsetsPending && win.mAttrs.providedInsets == null) {
+ if (win != null && win.mGivenInsetsPending) {
return mInsetsHint;
}
if (mInsetsHintStale) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1353ff0..e16d869 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3751,9 +3751,11 @@
// Boost the adjacent TaskFragment for dimmer if needed.
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && taskFragment.isEmbedded()) {
+ taskFragment.mDimmerSurfaceBoosted = false;
final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
adjacentTf.assignLayer(t, layer++);
+ adjacentTf.mDimmerSurfaceBoosted = true;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 78ababc..a818a72 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,6 +216,9 @@
Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
+ /** {@code true} if the dimmer surface is boosted. {@code false} otherwise. */
+ boolean mDimmerSurfaceBoosted;
+
/** Apply the dim layer on the embedded TaskFragment. */
static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ae5a5cb..a055db2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2109,7 +2109,15 @@
+ ", touchableRegion=" + w.mGivenTouchableRegion + " -> " + touchableRegion
+ ", touchableInsets " + w.mTouchableInsets + " -> " + touchableInsets);
if (w != null) {
+ final boolean wasGivenInsetsPending = w.mGivenInsetsPending;
w.mGivenInsetsPending = false;
+ if ((!wasGivenInsetsPending || !w.hasInsetsSourceProvider())
+ && w.mTouchableInsets == touchableInsets
+ && w.mGivenContentInsets.equals(contentInsets)
+ && w.mGivenVisibleInsets.equals(visibleInsets)
+ && w.mGivenTouchableRegion.equals(touchableRegion)) {
+ return;
+ }
w.mGivenContentInsets.set(contentInsets);
w.mGivenVisibleInsets.set(visibleInsets);
w.mGivenTouchableRegion.set(touchableRegion);
@@ -9214,6 +9222,11 @@
return false;
}
+ if (taskFragment.mDimmerSurfaceBoosted) {
+ // Skip if the TaskFragment currently has dimmer surface boosted.
+ return false;
+ }
+
final ActivityRecord topActivity =
taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 18ac0e7..1106a95 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1342,7 +1342,7 @@
// This window doesn't provide any insets.
return;
}
- if (mGivenInsetsPending && mAttrs.providedInsets == null) {
+ if (mGivenInsetsPending) {
// The given insets are pending, and they are not reliable for now. The source frame
// should be updated after the new given insets are sent to window manager.
return;
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 6d5fc80..b0e71bd 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -109,6 +109,9 @@
return;
}
synchronized (mEnabledLock) {
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
+ }
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
mBuffer.resetBuffer();
mEnabled = mEnabledLockFree = true;
@@ -136,6 +139,9 @@
writeTraceToFileLocked();
logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
}
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+ }
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 80046b60..c37946b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23389,7 +23389,7 @@
DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
}
- private boolean isUnicornFlagEnabled() {
+ static boolean isUnicornFlagEnabled() {
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index c108deaf..a7adc5b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -66,6 +66,10 @@
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+ if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
+ return true;
+ }
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
@@ -79,6 +83,10 @@
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
+ if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
+ return true;
+ }
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
diff --git a/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
new file mode 100644
index 0000000..e12e961f
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.os.Build;
+import android.test.mock.MockContext;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.net.module.util.ProxyUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class Ikev2VpnProfileTest {
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final String USERNAME_STRING = "username";
+ private static final String PASSWORD_STRING = "pa55w0rd";
+ private static final String EXCL_LIST = "exclList";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+ private static final int TEST_MTU = 1300;
+
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return "fooPackage";
+ }
+ };
+ private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
+ SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
+
+ private X509Certificate mUserCert;
+ private X509Certificate mServerRootCa;
+ private PrivateKey mPrivateKey;
+
+ @Before
+ public void setUp() throws Exception {
+ mServerRootCa = generateRandomCertAndKeyPair().cert;
+
+ final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
+ mUserCert = userCertKey.cert;
+ mPrivateKey = userCertKey.key;
+ }
+
+ private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
+
+ builder.setBypassable(true);
+ builder.setProxy(mProxy);
+ builder.setMaxMtu(TEST_MTU);
+ builder.setMetered(true);
+
+ return builder;
+ }
+
+ @Test
+ public void testBuildValidProfileWithOptions() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ // Check non-auth parameters correctly stored
+ assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
+ assertEquals(IDENTITY_STRING, profile.getUserIdentity());
+ assertEquals(mProxy, profile.getProxyInfo());
+ assertTrue(profile.isBypassable());
+ assertTrue(profile.isMetered());
+ assertEquals(TEST_MTU, profile.getMaxMtu());
+ assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildUsernamePasswordProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(USERNAME_STRING, profile.getUsername());
+ assertEquals(PASSWORD_STRING, profile.getPassword());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildDigitalSignatureProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(profile.getUserCert(), mUserCert);
+ assertEquals(mPrivateKey, profile.getRsaPrivateKey());
+ assertEquals(profile.getServerRootCaCert(), mServerRootCa);
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ }
+
+ @Test
+ public void testBuildPresharedKeyProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
+
+ assertNull(profile.getServerRootCaCert());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsAead() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.AUTH_AES_XCBC,
+ IpSecAlgorithm.AUTH_AES_CMAC,
+ IpSecAlgorithm.CRYPT_AES_CBC,
+ IpSecAlgorithm.CRYPT_AES_CTR);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsEmptyList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(new ArrayList<>());
+ fail("Expected exception due to no valid algorithm set");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInvalidList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ List<String> allowedAlgorithms = new ArrayList<>();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
+ fail("Expected exception due to missing encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
+ fail("Expected exception due to missing authentication");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ List<String> allowedAlgorithms = new ArrayList<>();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildNoAuthMethodSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.build();
+ fail("Expected exception due to lack of auth method");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+
+ // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @Test
+ public void testBuildExcludeLocalRoutesSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+ builder.setLocalRoutesExcluded(true);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+ assertTrue(profile.areLocalRoutesExcluded());
+
+ builder.setBypassable(false);
+ try {
+ builder.build();
+ fail("Expected exception because excludeLocalRoutes should be set only"
+ + " on the bypassable VPN");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildInvalidMtu() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setMaxMtu(500);
+ fail("Expected exception due to too-small MTU");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private void verifyVpnProfileCommon(VpnProfile profile) {
+ assertEquals(SERVER_ADDR_STRING, profile.server);
+ assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
+ assertEquals(mProxy, profile.proxy);
+ assertTrue(profile.isBypassable);
+ assertTrue(profile.isMetered);
+ assertEquals(TEST_MTU, profile.maxMtu);
+ }
+
+ @Test
+ public void testPskConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecCaCert);
+ }
+
+ @Test
+ public void testUsernamePasswordConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(USERNAME_STRING, profile.username);
+ assertEquals(PASSWORD_STRING, profile.password);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecSecret);
+ }
+
+ @Test
+ public void testRsaConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+ + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
+ assertEquals(
+ expectedSecret,
+ profile.ipsecSecret);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ }
+
+ @Test
+ public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+ profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ assertNull(result.getServerRootCaCert());
+ }
+
+ @Test
+ public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.ipsecSecret = new String(PSK_BYTES);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getPresharedKey());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ }
+
+ @Test
+ public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getPresharedKey());
+ }
+
+ @Test
+ public void testPskConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testUsernamePasswordConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testRsaConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
+ // Special keyId that contains delimiter character of VpnProfile
+ final byte[] keyId = "foo\0bar".getBytes();
+ final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
+ getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
+ CHILD_PARAMS);
+ final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+
+ assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
+
+ // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
+ // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
+ assertEquals("", vpnProfile.server);
+ assertEquals("", vpnProfile.ipsecIdentifier);
+ assertEquals("", vpnProfile.username);
+ assertEquals("", vpnProfile.password);
+ assertEquals("", vpnProfile.ipsecCaCert);
+ assertEquals("", vpnProfile.ipsecSecret);
+ assertEquals("", vpnProfile.ipsecUserCert);
+ assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
+
+ // IkeTunnelConnectionParams should stay the same.
+ assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
+
+ // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
+ final VpnProfile decodedVpnProfile =
+ VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
+ final Ikev2VpnProfile convertedIkev2VpnProfile =
+ Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
+ assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
+ }
+
+ @Test
+ public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ // Config authentication related fields is not required while building with
+ // IkeTunnelConnectionParams.
+ final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAutomaticNattKeepaliveTimerEnabled(true);
+ builder.setAutomaticIpVersionSelectionEnabled(true);
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionDefaults() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
+ assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ // Verify building without IkeTunnelConnectionParams
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ assertEquals(builder.build(), builder.build());
+
+ // Verify building with IkeTunnelConnectionParams
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ final IkeTunnelConnectionParams tunnelParams2 =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+ new Ikev2VpnProfile.Builder(tunnelParams2).build());
+ }
+
+ @Test
+ public void testBuildProfileWithNullProxy() throws Exception {
+ final Ikev2VpnProfile ikev2VpnProfile =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
+ .build();
+
+ // ProxyInfo should be null for the profile without setting ProxyInfo.
+ assertNull(ikev2VpnProfile.getProxyInfo());
+
+ // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+ assertNull(vpnProfile.proxy);
+
+ final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
+ assertNull(convertedIkev2VpnProfile.getProxyInfo());
+ }
+
+ private static class CertificateAndKey {
+ public final X509Certificate cert;
+ public final PrivateKey key;
+
+ CertificateAndKey(X509Certificate cert, PrivateKey key) {
+ this.cert = cert;
+ this.key = key;
+ }
+ }
+
+ private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+ final Date validityBeginDate =
+ new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+ final Date validityEndDate =
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+ // Generate a keypair
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(512);
+ final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ final X500Principal dnName = new X500Principal("CN=test.android.com");
+ final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+ certGen.setSubjectDN(dnName);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(validityBeginDate);
+ certGen.setNotAfter(validityEndDate);
+ certGen.setPublicKey(keyPair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+ return new CertificateAndKey(cert, keyPair.getPrivate());
+ }
+}
diff --git a/services/tests/VpnTests/java/android/net/VpnManagerTest.java b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
new file mode 100644
index 0000000..365b4d1
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.test.mock.MockContext;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.MessageUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link VpnManager}. */
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class VpnManagerTest {
+
+ private static final String PKG_NAME = "fooPackage";
+
+ private static final String SESSION_NAME_STRING = "testSession";
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+
+ private IVpnManager mMockService;
+ private VpnManager mVpnManager;
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return PKG_NAME;
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ assumeFalse("Skipping test because watches don't support VPN",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH));
+ mMockService = mock(IVpnManager.class);
+ mVpnManager = new VpnManager(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testProvisionVpnProfilePreconsented() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(true);
+
+ // Expect there to be no intent returned, as consent has already been granted.
+ assertNull(mVpnManager.provisionVpnProfile(profile));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testProvisionVpnProfileNeedsConsent() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(false);
+
+ // Expect intent to be returned, as consent has not already been granted.
+ final Intent intent = mVpnManager.provisionVpnProfile(profile);
+ assertNotNull(intent);
+
+ final ComponentName expectedComponentName =
+ ComponentName.unflattenFromString(
+ "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
+ assertEquals(expectedComponentName, intent.getComponent());
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testDeleteProvisionedVpnProfile() throws Exception {
+ mVpnManager.deleteProvisionedVpnProfile();
+ verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStartProvisionedVpnProfile() throws Exception {
+ mVpnManager.startProvisionedVpnProfile();
+ verify(mMockService).startVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStopProvisionedVpnProfile() throws Exception {
+ mVpnManager.stopProvisionedVpnProfile();
+ verify(mMockService).stopVpnProfile(eq(PKG_NAME));
+ }
+
+ private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
+ return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setBypassable(true)
+ .setMaxMtu(1300)
+ .setMetered(true)
+ .setAuthPsk(PSK_BYTES)
+ .build();
+ }
+
+ @Test
+ public void testVpnTypesEqual() throws Exception {
+ SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
+ new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
+ SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
+ new Class[] { NativeVpnType.class }, new String[]{ "" });
+
+ // TYPE_VPN_NONE = -1 is only defined in VpnManager.
+ assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
+ for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
+ assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
+ }
+ }
+}
diff --git a/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
new file mode 100644
index 0000000..acae7d2
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
+
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link VpnProfile}. */
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class VpnProfileTest {
+ private static final String DUMMY_PROFILE_KEY = "Test";
+
+ private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
+ private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+ private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
+ private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
+ private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
+ private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
+ private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
+
+ @Test
+ public void testDefaults() throws Exception {
+ final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
+
+ assertEquals(DUMMY_PROFILE_KEY, p.key);
+ assertEquals("", p.name);
+ assertEquals(VpnProfile.TYPE_PPTP, p.type);
+ assertEquals("", p.server);
+ assertEquals("", p.username);
+ assertEquals("", p.password);
+ assertEquals("", p.dnsServers);
+ assertEquals("", p.searchDomains);
+ assertEquals("", p.routes);
+ assertTrue(p.mppe);
+ assertEquals("", p.l2tpSecret);
+ assertEquals("", p.ipsecIdentifier);
+ assertEquals("", p.ipsecSecret);
+ assertEquals("", p.ipsecUserCert);
+ assertEquals("", p.ipsecCaCert);
+ assertEquals("", p.ipsecServerCert);
+ assertEquals(null, p.proxy);
+ assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
+ assertFalse(p.isBypassable);
+ assertFalse(p.isMetered);
+ assertEquals(1360, p.maxMtu);
+ assertFalse(p.areAuthParamsInline);
+ assertFalse(p.isRestrictedToTestNetworks);
+ assertFalse(p.excludeLocalRoutes);
+ assertFalse(p.requiresInternetValidation);
+ assertFalse(p.automaticNattKeepaliveTimerEnabled);
+ assertFalse(p.automaticIpVersionSelectionEnabled);
+ }
+
+ private VpnProfile getSampleIkev2Profile(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+ p.server = "bar";
+ p.username = "baz";
+ p.password = "qux";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.l2tpSecret = "";
+ p.ipsecIdentifier = "quux";
+ p.ipsecSecret = "quuz";
+ p.ipsecUserCert = "corge";
+ p.ipsecCaCert = "grault";
+ p.ipsecServerCert = "garply";
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
+ true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.server = "bar";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(
+ getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
+
+ final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ modified.maxMtu--;
+ assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ if (isAtLeastU()) {
+ // automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled added in U.
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
+ } else if (isAtLeastT()) {
+ // excludeLocalRoutes, requiresPlatformValidation were added in T.
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
+ } else {
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
+ }
+ }
+
+ @Test
+ public void testEncodeDecodeWithIkeTunConnParams() {
+ final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecode() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecodeTooManyValues() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final byte[] tooManyValues =
+ (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
+ }
+
+ private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
+ // Sort to ensure when we remove, we can do it from greatest first.
+ Arrays.sort(missingIndices);
+
+ final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
+ final List<String> parts =
+ new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
+
+ // Remove from back first to ensure indexing is consistent.
+ for (int i = missingIndices.length - 1; i >= 0; i--) {
+ parts.remove(missingIndices[i]);
+ }
+
+ return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
+ }
+
+ @Test
+ public void testEncodeDecodeInvalidNumberOfValues() {
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_AUTH_PARAMS_INLINE,
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
+ /* missingIndices */);
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
+ }
+
+ private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
+ return getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without isRestrictedToTestNetworks defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.isRestrictedToTestNetworks);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingExcludeLocalRoutes() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without excludeLocalRoutes defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.excludeLocalRoutes);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingRequiresValidation() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without requiresValidation defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.requiresInternetValidation);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticIpVersionSelectionEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeLoginsNotSaved() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ profile.saveLogin = false;
+
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertNotEquals(profile, decoded);
+
+ // Add the username/password back, everything else must be equal.
+ decoded.username = profile.username;
+ decoded.password = profile.password;
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testClone() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile clone = profile.clone();
+ assertEquals(profile, clone);
+ assertNotSame(profile, clone);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
rename to services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3f6117b..e141faf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2001,6 +2001,59 @@
}
@Test
+ public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
+ // Legacy stack doesn't support deferral
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ setProcessFreezable(receiverGreenApp, true, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ final BroadcastOptions opts = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 10);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 5);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp, 0);
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ // Enqueue the broadcast again to replace the earlier one
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ waitForIdle();
+ // Green should still be in the cached state and shouldn't receive the broadcast
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+
+ final IApplicationThread blueThread = receiverBlueApp.getThread();
+ final IApplicationThread yellowThread = receiverYellowApp.getThread();
+ final InOrder inOrder = inOrder(blueThread, yellowThread);
+ inOrder.verify(blueThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ inOrder.verify(yellowThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+
+ setProcessFreezable(receiverGreenApp, false, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ // Confirm that green receives the broadcast once it comes out of the cached state
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index 4db27d2..dc26e6e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -17,9 +17,9 @@
package com.android.server.accessibility;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -27,11 +27,13 @@
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +44,7 @@
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Region;
import android.os.IBinder;
import android.os.LocaleList;
@@ -63,6 +66,7 @@
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -117,9 +121,8 @@
// List of window token, mapping from windowId -> window token.
private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> window info lists.
- private final SparseArray<ArrayList<WindowInfo>> mWindowInfos =
- new SparseArray<>();
+ // List of window info lists, mapping from displayId -> a11y window lists.
+ private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
// List of callback, mapping from displayId -> callback.
private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
new SparseArray<>();
@@ -129,6 +132,13 @@
private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ // This maps displayId -> next region offset.
+ // Touchable region must have un-occluded area so that it's exposed to a11y services.
+ // This offset can be used as left and top of new region so that top-left of each region are
+ // kept visible.
+ // It's expected to be incremented by some amount everytime the value is used.
+ private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
+
@Mock
private WindowManagerInternal mMockWindowManagerInternal;
@Mock
@@ -162,7 +172,7 @@
anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
doAnswer((invocation) -> {
- onWindowsForAccessibilityChanged(invocation.getArgument(0), false);
+ onAccessibilityWindowsChanged(invocation.getArgument(0), false);
return null;
}).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
@@ -176,7 +186,7 @@
// as top focused display before each testing starts.
startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
- // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
+ // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
// Resets it for mockito verify of further test case.
Mockito.reset(mMockA11yEventSender);
@@ -240,19 +250,18 @@
@Test
public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ final WindowInfo focusedWindowInfo =
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, focusedWindowInfo.token));
focusedWindowInfo.focused = false;
- focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1);
- focusedWindowInfo.focused = true;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
mA11yWindowManager.onTouchInteractionStart();
setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
}
@@ -276,7 +285,7 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should not be changed.
assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should not be changed.
@@ -304,8 +313,8 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should be changed.
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should be changed.
@@ -315,53 +324,43 @@
@Test
public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
- assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1,
+ assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
is(a11yWindow.getLayer()));
}
}
@Test
public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
final IBinder windowToken = mA11yWindowManager
.getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(i).getWindowInfo();
assertThat(windowToken, is(windowInfo.token));
}
}
@Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ assertNotEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertNotEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
- }
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
- @Test
- public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
-
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ assertEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
}
@Test
@@ -371,14 +370,10 @@
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
true, USER_SYSTEM_ID);
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = token.asBinder();
- windowInfo.layer = 0;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo);
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0,
+ createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(oldWindow,
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
}
@@ -386,12 +381,12 @@
@Test
public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
final WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
focusedWindowInfo.focused = false;
windowInfo.focused = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
.isFocused());
}
@@ -500,15 +495,18 @@
@Test
public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
// Updates top 2 z-order WindowInfo are whole visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
- windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1);
- windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2,
- SCREEN_WIDTH, SCREEN_HEIGHT);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(0).getId();
@@ -526,12 +524,17 @@
@Test
public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
// Updates z-order #1 WindowInfo is half visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -542,9 +545,17 @@
@Test
public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ // Note that the second window is also exposed even if region is empty because it's focused.
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -555,16 +566,21 @@
@Test
public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
// Updates z-order #0 WindowInfo to have two interact-able areas.
- Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+ final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(region);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow, region);
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
+ final int windowId = a11yWindows.get(1).getId();
mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
assertFalse(outBounds.getBounds().isEmpty());
@@ -575,7 +591,8 @@
@Test
public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
final IBinder eventWindowToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token;
+ mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, eventWindowToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -766,7 +783,8 @@
public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
throws RemoteException {
final IBinder defaultFocusWinToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, defaultFocusWinToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -811,8 +829,8 @@
@Test
public void getPictureInPictureWindow_shouldNotNull() {
assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
}
@@ -826,8 +844,9 @@
final IAccessibilityInteractionConnection mockRemoteConnection =
mA11yWindowManager.getConnectionLocked(
USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
+ true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
verify(mockRemoteConnection).notifyOutsideTouch();
@@ -945,18 +964,14 @@
@Test
public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
// Removing index 0 because it's not focused, and avoids unnecessary layer change.
final int windowId =
getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- infos.remove(0);
- for (WindowInfo info : infos) {
- // Adjust layer number because it should start from 0.
- info.layer--;
- }
+ windows.remove(0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -970,21 +985,15 @@
@Test
public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
-
- for (WindowInfo info : infos) {
- // Adjust layer number because new window will have 0 so that layer number in
- // A11yWindowInfo in window won't be changed.
- info.layer++;
- }
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
false, USER_SYSTEM_ID);
- addWindowInfo(infos, token, 0);
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);
+ // Adding window to the front so that other windows' layer won't change.
+ windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+ final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -998,11 +1007,11 @@
@Test
public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
- infos.get(0).title = "new title";
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+ windows.get(0).getWindowInfo().title = "new title";
final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -1020,42 +1029,41 @@
}
private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
+ ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
// mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
// for the test.
- int layer = 0;
for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
true, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
for (int i = 0; i < NUM_APP_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
false, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
// Sets up current focused window of display.
// Each display has its own current focused window if config_perDisplayFocusEnabled is true.
// Otherwise only default display needs to current focused window.
if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true;
+ windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
}
// Turns on windows tracking, and update window info.
mA11yWindowManager.startTrackingWindows(displayId, false);
// Puts window lists into array.
- mWindowInfos.put(displayId, windowInfosForDisplay);
+ mWindows.put(displayId, windowsForDisplay);
// Sets the default display is the top focused display and
// its current focused window is the top focused window.
if (displayId == Display.DEFAULT_DISPLAY) {
setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
}
// Invokes callback for sending window lists to A11y framework.
- onWindowsForAccessibilityChanged(displayId, FORCE_SEND);
+ onAccessibilityWindowsChanged(displayId, FORCE_SEND);
assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowInfosForDisplay.size());
+ windowsForDisplay.size());
}
private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
@@ -1109,36 +1117,28 @@
return windowId;
}
- private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
- windowInfo.layer = layer;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- windowInfos.add(windowInfo);
- }
-
private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindowInfos.get(displayId).get(index).token;
+ final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
return mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, windowToken);
}
private void setTopFocusedWindowAndDisplay(int displayId, int index) {
// Sets the top focus window.
- mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token;
+ mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
// Sets the top focused display.
mTopFocusedDisplayId = displayId;
}
- private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) {
+ private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
if (callbacks == null) {
callbacks = getWindowsForAccessibilityCallbacks(displayId);
mCallbackOfWindows.put(displayId, callbacks);
}
- callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, mWindowInfos.get(displayId));
+ callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
+ mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
+ mWindows.get(displayId));
}
private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
@@ -1147,23 +1147,23 @@
if (mSupportPerDisplayFocus) {
// Gets the old focused window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Gets the new window of display which wants to change focused window.
focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
} else {
// Gets the window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
// Gets the old focused window of old top focused display.
focusedWindowInfo =
- mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Changes the top focused display and window.
@@ -1171,6 +1171,42 @@
}
}
+ private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ // TODO(b/325341171): add tests with various kinds of windows such as
+ // changing window types, touchable or not, trusted or not, etc.
+ windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ windowInfo.token = windowToken.asBinder();
+
+ final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
+ when(window.getWindowInfo()).thenReturn(windowInfo);
+ when(window.ignoreRecentsAnimationForAccessibility()).thenReturn(false);
+ when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
+ when(window.isTouchable()).thenReturn(true);
+ when(window.getType()).thenReturn(windowInfo.type);
+
+ setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
+ return window;
+ }
+
+ private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInScreen(any(Region.class));
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInWindow(any(Region.class));
+ }
+
+ private Region nextToucableRegion(int displayId) {
+ final int topLeft = mNextRegionOffsets.get(displayId, 0);
+ final int bottomRight = topLeft + 100;
+ mNextRegionOffsets.put(displayId, topLeft + 10);
+ return new Region(topLeft, topLeft, bottomRight, bottomRight);
+ }
+
@Nullable
private static String toString(@Nullable CharSequence cs) {
return cs == null ? null : cs.toString();
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index e0ef035..a6f2196 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -30,6 +30,7 @@
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
@@ -37,6 +38,7 @@
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -67,6 +69,9 @@
@RunWith(AndroidJUnit4.class)
public class BugreportManagerServiceImplTest {
+ private static final UserInfo ADMIN_USER_INFO =
+ new UserInfo(/* id= */ 5678, "adminUser", UserInfo.FLAG_ADMIN);
+
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -82,6 +87,8 @@
@Mock
private DevicePolicyManager mMockDevicePolicyManager;
+ private TestInjector mInjector;
+
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
private AtomicFile mMappingFile;
@@ -96,9 +103,9 @@
mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
- mService = new BugreportManagerServiceImpl(
- new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager);
+ mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
// The calling user is an admin user by default.
@@ -190,6 +197,33 @@
}
@Test
+ public void testStartBugreport() throws Exception {
+ mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false);
+
+ assertThat(mInjector.isBugreportStarted()).isTrue();
+ }
+
+ @Test
+ public void testStartBugreport_nonAdminProfileOfAdminCurrentUser() throws Exception {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ when(mMockUserManager.isUserAdmin(callingUserId)).thenReturn(false);
+ when(mMockUserManager.getProfileParent(callingUserId)).thenReturn(ADMIN_USER_INFO);
+
+ mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false);
+
+ assertThat(mInjector.isBugreportStarted()).isTrue();
+ }
+
+ @Test
public void testStartBugreport_throwsForNonAdminUser() throws Exception {
when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
@@ -317,8 +351,12 @@
private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+ private static final String SYSTEM_PROPERTY_BUGREPORT_START = "ctl.start";
+ private static final String SYSTEM_PROPERTY_BUGREPORT_STOP = "ctl.stop";
+
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
+ private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
UserManager um, DevicePolicyManager dpm) {
@@ -336,5 +374,20 @@
public DevicePolicyManager getDevicePolicyManager() {
return mDevicePolicyManager;
}
+
+ @Override
+ public void setSystemProperty(String key, String value) {
+ // Calling SystemProperties.set() will throw a RuntimeException due to permission error.
+ // Instead, we are just marking a flag to store the state for testing.
+ if (SYSTEM_PROPERTY_BUGREPORT_START.equals(key)) {
+ mBugreportStarted = true;
+ } else if (SYSTEM_PROPERTY_BUGREPORT_STOP.equals(key)) {
+ mBugreportStarted = false;
+ }
+ }
+
+ public boolean isBugreportStarted() {
+ return mBugreportStarted;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 01bd96b..5360a10 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -896,6 +896,11 @@
assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
// The focus should NOT change.
assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+ // Do not move focus if the dim is boosted.
+ taskFragmentLeft.mDimmerSurfaceBoosted = true;
+ assertFalse(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
}
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2fc6a22..8b503f2 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -28,6 +28,8 @@
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.RemoteServiceCallback;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
import java.util.ArrayList;
import java.util.HashMap;
@@ -550,6 +552,9 @@
private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
+ /** Telecom feature flags **/
+ private final FeatureFlags mTelecomFeatureFlags = new FeatureFlagsImpl();
+
RemoteConnectionService(
IConnectionService outgoingConnectionServiceRpc,
ConnectionService ourConnectionServiceImpl) throws RemoteException {
@@ -578,6 +583,14 @@
extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
+ // Defaulted ConnectionRequest params
+ String telecomCallId = "";
+ boolean shouldShowIncomingUI = false;
+ if (mTelecomFeatureFlags.setRemoteConnectionCallId()) {
+ telecomCallId = id;
+ shouldShowIncomingUI = request.shouldShowIncomingCallUi();
+ }
+
final ConnectionRequest newRequest = new ConnectionRequest.Builder()
.setAccountHandle(request.getAccountHandle())
.setAddress(request.getAddress())
@@ -585,6 +598,9 @@
.setVideoState(request.getVideoState())
.setRttPipeFromInCall(request.getRttPipeFromInCall())
.setRttPipeToInCall(request.getRttPipeToInCall())
+ // Flagged changes
+ .setTelecomCallId(telecomCallId)
+ .setShouldShowIncomingCallUi(shouldShowIncomingUI)
.build();
try {
if (mConnectionById.isEmpty()) {
@@ -626,10 +642,28 @@
mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
null /*Session.Info*/);
}
+
+ // Set telecom call id to what's being tracked by base ConnectionService.
+ String telecomCallId = mTelecomFeatureFlags.setRemoteConnectionCallId()
+ ? id : request.getTelecomCallId();
+
+ final ConnectionRequest newRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(request.getAccountHandle())
+ .setAddress(request.getAddress())
+ .setExtras(request.getExtras())
+ .setVideoState(request.getVideoState())
+ .setShouldShowIncomingCallUi(request.shouldShowIncomingCallUi())
+ .setRttPipeFromInCall(request.getRttPipeFromInCall())
+ .setRttPipeToInCall(request.getRttPipeToInCall())
+ .setParticipants(request.getParticipants())
+ .setIsAdhocConferenceCall(request.isAdhocConferenceCall())
+ .setTelecomCallId(telecomCallId)
+ .build();
+
RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
id,
- request,
+ newRequest,
isIncoming,
false /* isUnknownCall */,
null /*Session.info*/);
@@ -640,7 +674,7 @@
maybeDisconnectAdapter();
}
});
- conference.putExtras(request.getExtras());
+ conference.putExtras(newRequest.getExtras());
return conference;
} catch (RemoteException e) {
return RemoteConference.failure(
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5d99acd..2150b5d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5307,6 +5307,19 @@
KEY_PREFIX + "enable_presence_group_subscribe_bool";
/**
+ * SIP SUBSCRIBE retry duration used when device doesn't receive a response to SIP
+ * SUBSCRIBE request.
+ * If this value is not defined or defined as negative value, the device does not retry
+ * the SIP SUBSCRIBE.
+ * If the value is 0 then device retries immediately upon timeout.
+ * If the value is > 0 then device waits for configured duration and retries after timeout
+ * is detected
+ * @hide
+ */
+ public static final String KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG =
+ KEY_PREFIX + "subscribe_retry_duration_millis_long";
+
+ /**
* Flag indicating whether or not to use SIP URI when send a presence subscribe.
* When {@code true}, the device sets the To and Contact header to be SIP URI using
* the TelephonyManager#getIsimDomain" API.
@@ -5982,6 +5995,7 @@
defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
+ defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
diff --git a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
index 407d4bf..2977c21 100644
--- a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
+++ b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
@@ -30,7 +30,7 @@
void setBuffer(AHardwareBuffer* buffer) {
ASurfaceTransaction* transaction = ASurfaceTransaction_create();
- ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer);
+ ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer, -1);
ASurfaceTransaction_setVisibility(transaction, surfaceControl,
ASURFACE_TRANSACTION_VISIBILITY_SHOW);
ASurfaceTransaction_apply(transaction);
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 58638e8..45ab986 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -718,6 +718,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IClientInterface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "setupInterfaceForClientMode NullPointerException");
+ return false;
}
if (clientInterface == null) {
@@ -785,6 +788,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown client interface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "tearDownClientInterface NullPointerException");
+ return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown client interface");
@@ -816,6 +822,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IApInterface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "setupInterfaceForSoftApMode NullPointerException");
+ return false;
}
if (apInterface == null) {
@@ -854,6 +863,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown AP interface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "tearDownSoftApInterface NullPointerException");
+ return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown AP interface");
@@ -1328,6 +1340,8 @@
}
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "getChannelsMhzForBand NullPointerException");
}
if (result == null) {
result = new int[0];
@@ -1352,7 +1366,8 @@
*/
@Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
if (mWificond == null) {
- Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?");
+ Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! "
+ + "Did wificond die?");
return null;
}
@@ -1360,6 +1375,9 @@
return mWificond.getDeviceWiphyCapabilities(ifaceName);
} catch (RemoteException e) {
return null;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "getDeviceWiphyCapabilities NullPointerException");
+ return null;
}
}
@@ -1409,6 +1427,8 @@
Log.i(TAG, "Receive country code change to " + newCountryCode);
} catch (RemoteException re) {
re.rethrowFromSystemServer();
+ } catch (NullPointerException e) {
+ new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer();
}
}