Merge "Require CREATE_VIRTUAL_DEVICE permission to register or unregister an AccessibilityDisplayProxy" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 29ab455..e60ed4a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -727,8 +727,11 @@
// Exception-throwing-can down the road to JobParameters.completeWork >:(
return true;
}
- mService.mJobs.touchJob(mRunningJob);
- return mRunningJob.completeWorkLocked(workId);
+ if (mRunningJob.completeWorkLocked(workId)) {
+ mService.mJobs.touchJob(mRunningJob);
+ return true;
+ }
+ return false;
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 1971a11..537a670 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -828,6 +828,10 @@
}
}
+ /**
+ * Returns {@code true} if the JobWorkItem queue was updated,
+ * and {@code false} if nothing changed.
+ */
public boolean completeWorkLocked(int workId) {
if (executingWork != null) {
final int N = executingWork.size();
diff --git a/core/api/current.txt b/core/api/current.txt
index 8a6bb7b..2db3eae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6784,7 +6784,7 @@
method public boolean areNotificationsEnabled();
method public boolean areNotificationsPaused();
method public boolean canNotifyAsPackage(@NonNull String);
- method public boolean canSendFullScreenIntent();
+ method public boolean canUseFullScreenIntent();
method public void cancel(int);
method public void cancel(@Nullable String, int);
method public void cancelAll();
@@ -19527,9 +19527,9 @@
public final class DisplayManager {
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int);
- method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int);
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
- method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
method public android.view.Display getDisplay(int);
method public android.view.Display[] getDisplays();
method public android.view.Display[] getDisplays(String);
@@ -19584,6 +19584,30 @@
method public void onStopped();
}
+ public final class VirtualDisplayConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getDensityDpi();
+ method @NonNull public java.util.List<java.lang.String> getDisplayCategories();
+ method public int getFlags();
+ method public int getHeight();
+ method @NonNull public String getName();
+ method public float getRequestedRefreshRate();
+ method @Nullable public android.view.Surface getSurface();
+ method public int getWidth();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.VirtualDisplayConfig> CREATOR;
+ }
+
+ public static final class VirtualDisplayConfig.Builder {
+ ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int);
+ method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String);
+ method @NonNull public android.hardware.display.VirtualDisplayConfig build();
+ method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int);
+ method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float);
+ method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setSurface(@Nullable android.view.Surface);
+ }
+
}
package android.hardware.fingerprint {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3432ba4..3307a4f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3217,8 +3217,8 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
- method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
- method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+ method @Deprecated @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
@@ -3347,10 +3347,15 @@
public interface VirtualSensorCallback {
method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+ method public default void onDirectChannelConfigured(@IntRange(from=1) int, @NonNull android.companion.virtual.sensor.VirtualSensor, int, @IntRange(from=1) int);
+ method public default void onDirectChannelCreated(@IntRange(from=1) int, @NonNull android.os.SharedMemory);
+ method public default void onDirectChannelDestroyed(@IntRange(from=1) int);
}
public final class VirtualSensorConfig implements android.os.Parcelable {
method public int describeContents();
+ method public int getDirectChannelTypesSupported();
+ method public int getHighestDirectReportRateLevel();
method @NonNull public String getName();
method public int getType();
method @Nullable public String getVendor();
@@ -3361,6 +3366,8 @@
public static final class VirtualSensorConfig.Builder {
ctor public VirtualSensorConfig.Builder(int, @NonNull String);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
}
@@ -10023,7 +10030,7 @@
method public int describeContents();
method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo();
method public int getNetworkSource();
- method @NonNull public int[] getSecurityTypes();
+ method @NonNull public java.util.Set<java.lang.Integer> getSecurityTypes();
method @NonNull public String getSsid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.KnownNetwork> CREATOR;
@@ -10033,10 +10040,10 @@
public static final class KnownNetwork.Builder {
ctor public KnownNetwork.Builder();
+ method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder addSecurityType(int);
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build();
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo);
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int);
- method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSecurityTypes(@NonNull int[]);
method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String);
}
@@ -10105,7 +10112,7 @@
method public long getDeviceId();
method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo();
method @Nullable public String getHotspotBssid();
- method @Nullable public int[] getHotspotSecurityTypes();
+ method @NonNull public java.util.Set<java.lang.Integer> getHotspotSecurityTypes();
method @Nullable public String getHotspotSsid();
method @NonNull public String getNetworkName();
method public int getNetworkType();
@@ -10119,11 +10126,11 @@
public static final class TetherNetwork.Builder {
ctor public TetherNetwork.Builder();
+ method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder addHotspotSecurityType(int);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork build();
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceId(long);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotBssid(@NonNull String);
- method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSecurityTypes(@NonNull int[]);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSsid(@NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkName(@NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkType(int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b0929b5..50275ab 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -36,6 +36,7 @@
import static android.window.ConfigurationHelper.shouldUpdateResources;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -49,6 +50,7 @@
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
+import android.app.compat.CompatChanges;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.ActivityRelaunchItem;
@@ -206,6 +208,7 @@
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.DecorView;
import com.android.internal.util.ArrayUtils;
@@ -219,6 +222,7 @@
import dalvik.system.CloseGuard;
import dalvik.system.VMDebug;
import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
import libcore.io.ForwardingOs;
import libcore.io.IoUtils;
@@ -6703,6 +6707,11 @@
// Let libcore handle any compat changes after installing the list of compat changes.
AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
+ // Initialize the zip path validator callback depending on the targetSdk.
+ // This has to be after AppCompatCallbacks#install() so that the Compat
+ // checks work accordingly.
+ initZipPathValidatorCallback();
+
mBoundApplication = data;
mConfigurationController.setConfiguration(data.config);
mConfigurationController.setCompatConfiguration(data.config);
@@ -7070,6 +7079,18 @@
}
}
+ /**
+ * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+ * entry names.
+ * Otherwise: clear the callback to the default validation.
+ */
+ private void initZipPathValidatorCallback() {
+ if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+ ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+ } else {
+ ZipPathValidator.clearCallback();
+ }
+ }
private void handleSetContentCaptureOptionsCallback(String packageName) {
if (mContentCaptureOptionsCallback != null) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index fe40a4c..f48181b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -60,7 +60,6 @@
private String[] mRequireNoneOfPermissions;
private long mRequireCompatChangeId = CHANGE_INVALID;
private long mIdForResponseEvent;
- private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
private @Nullable String mDeliveryGroupMatchingKey;
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
@@ -190,12 +189,6 @@
"android:broadcast.idForResponseEvent";
/**
- * Corresponds to {@link #setRemoveMatchingFilter}.
- */
- private static final String KEY_REMOVE_MATCHING_FILTER =
- "android:broadcast.removeMatchingFilter";
-
- /**
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
*/
private static final String KEY_DELIVERY_GROUP_POLICY =
@@ -274,18 +267,6 @@
}
/**
- * {@hide}
- * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead.
- */
- @Deprecated
- public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
- @NonNull IntentFilter removeMatchingFilter) {
- BroadcastOptions opts = new BroadcastOptions();
- opts.setRemoveMatchingFilter(removeMatchingFilter);
- return opts;
- }
-
- /**
* Creates a new {@code BroadcastOptions} with no options initially set.
*/
public BroadcastOptions() {
@@ -315,8 +296,6 @@
mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
- mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
- IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
@@ -797,31 +776,6 @@
}
/**
- * When enqueuing this broadcast, remove all pending broadcasts previously
- * sent by this app which match the given filter.
- * <p>
- * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
- * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
- *
- * @hide
- * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead.
- */
- @Deprecated
- public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
- mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
- }
-
- /** @hide */
- public void clearRemoveMatchingFilter() {
- mRemoveMatchingFilter = null;
- }
-
- /** @hide */
- public @Nullable IntentFilter getRemoveMatchingFilter() {
- return mRemoveMatchingFilter;
- }
-
- /**
* Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
*
@@ -1092,9 +1046,6 @@
if (mIdForResponseEvent != 0) {
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
}
- if (mRemoveMatchingFilter != null) {
- b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
- }
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f653e13..5c38c42 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -341,12 +341,6 @@
in String message, boolean force, int exceptionTypeId);
void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName,
int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras);
- /** @deprecated -- use getProviderMimeTypeAsync */
- @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives =
- "Use {@link android.content.ContentResolver#getType} public API instead.")
- String getProviderMimeType(in Uri uri, int userId);
-
- oneway void getProviderMimeTypeAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
// Cause the specified process to dump the specified heap.
boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 91efa75..d2f2c3c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -873,7 +873,7 @@
* permission to your manifest, and use
* {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}.
*/
- public boolean canSendFullScreenIntent() {
+ public boolean canUseFullScreenIntent() {
final int result = PermissionChecker.checkPermissionForPreflight(mContext,
android.Manifest.permission.USE_FULL_SCREEN_INTENT,
mContext.getAttributionSource());
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 4d2c7cb..6cc4c8a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -89,14 +89,6 @@
private static final String TAG = "VirtualDeviceManager";
- private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
- DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
-
/**
* Broadcast Action: A Virtual Device was removed.
*
@@ -539,7 +531,11 @@
* not create the virtual display.
*
* @see DisplayManager#createVirtualDisplay
+ *
+ * @deprecated use {@link #createVirtualDisplay(VirtualDisplayConfig, Executor,
+ * VirtualDisplay.Callback)}
*/
+ @Deprecated
@Nullable
public VirtualDisplay createVirtualDisplay(
@IntRange(from = 1) int width,
@@ -552,30 +548,16 @@
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
getVirtualDisplayName(), width, height, densityDpi)
.setSurface(surface)
- .setFlags(getVirtualDisplayFlags(flags))
+ .setFlags(flags)
.build();
- return createVirtualDisplayInternal(config, executor, callback);
+ return createVirtualDisplay(config, executor, callback);
}
/**
* Creates a virtual display for this virtual device. All displays created on the same
* device belongs to the same display group.
*
- * @param width The width of the virtual display in pixels, must be greater than 0.
- * @param height The height of the virtual display in pixels, must be greater than 0.
- * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
- * @param displayCategories The categories of the virtual display, indicating the type of
- * activities allowed to run on the display. Activities can declare their type using
- * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
- * @param surface The surface to which the content of the virtual display should
- * be rendered, or null if there is none initially. The surface can also be set later using
- * {@link VirtualDisplay#setSurface(Surface)}.
- * @param flags A combination of virtual display flags accepted by
- * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
- * automatically set for all virtual devices:
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+ * @param config The configuration of the display.
* @param executor The executor on which {@code callback} will be invoked. This is ignored
* if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
* not be null.
@@ -587,28 +569,6 @@
*/
@Nullable
public VirtualDisplay createVirtualDisplay(
- @IntRange(from = 1) int width,
- @IntRange(from = 1) int height,
- @IntRange(from = 1) int densityDpi,
- @NonNull List<String> displayCategories,
- @Nullable Surface surface,
- @VirtualDisplayFlag int flags,
- @Nullable @CallbackExecutor Executor executor,
- @Nullable VirtualDisplay.Callback callback) {
- VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
- getVirtualDisplayName(), width, height, densityDpi)
- .setDisplayCategories(displayCategories)
- .setSurface(surface)
- .setFlags(getVirtualDisplayFlags(flags))
- .build();
- return createVirtualDisplayInternal(config, executor, callback);
- }
-
- /**
- * @hide
- */
- @Nullable
- private VirtualDisplay createVirtualDisplayInternal(
@NonNull VirtualDisplayConfig config,
@Nullable @CallbackExecutor Executor executor,
@Nullable VirtualDisplay.Callback callback) {
@@ -897,16 +857,6 @@
}
}
- /**
- * Returns the display flags that should be added to a particular virtual display.
- * Additional device-level flags from {@link
- * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will
- * be added by DisplayManagerService.
- */
- private int getVirtualDisplayFlags(int flags) {
- return DEFAULT_VIRTUAL_DISPLAY_FLAGS | flags;
- }
-
private String getVirtualDisplayName() {
try {
// Currently this just use the device ID, which means all of the virtual displays
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d8076b5..9f3b601 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -35,6 +35,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SharedMemory;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -577,6 +578,25 @@
mExecutor.execute(() -> mCallback.onConfigurationChanged(
sensor, enabled, samplingPeriod, batchReportingLatency));
}
+
+ @Override
+ public void onDirectChannelCreated(int channelHandle,
+ @NonNull SharedMemory sharedMemory) {
+ mExecutor.execute(
+ () -> mCallback.onDirectChannelCreated(channelHandle, sharedMemory));
+ }
+
+ @Override
+ public void onDirectChannelDestroyed(int channelHandle) {
+ mExecutor.execute(() -> mCallback.onDirectChannelDestroyed(channelHandle));
+ }
+
+ @Override
+ public void onDirectChannelConfigured(int channelHandle, @NonNull VirtualSensor sensor,
+ int rateLevel, int reportToken) {
+ mExecutor.execute(() -> mCallback.onDirectChannelConfigured(
+ channelHandle, sensor, rateLevel, reportToken));
+ }
}
/**
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
index 7da9c32..3cb0572 100644
--- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
@@ -17,6 +17,7 @@
package android.companion.virtual.sensor;
import android.companion.virtual.sensor.VirtualSensor;
+import android.os.SharedMemory;
/**
* Interface for notifying the sensor owner about whether and how sensor events should be injected.
@@ -36,4 +37,31 @@
*/
void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros,
int batchReportLatencyMicros);
+
+ /**
+ * Called when a sensor direct channel is created.
+ *
+ * @param channelHandle Identifier of the channel that was created.
+ * @param sharedMemory The shared memory region for the direct sensor channel.
+ */
+ void onDirectChannelCreated(int channelHandle, in SharedMemory sharedMemory);
+
+ /**
+ * Called when a sensor direct channel is destroyed.
+ *
+ * @param channelHandle Identifier of the channel that was destroyed.
+ */
+ void onDirectChannelDestroyed(int channelHandle);
+
+ /**
+ * Called when a sensor direct channel is configured.
+ *
+ * @param channelHandle Identifier of the channel that was configured.
+ * @param sensor The sensor, for which the channel was configured.
+ * @param rateLevel The rate level used to configure the direct sensor channel.
+ * @param reportToken A positive sensor report token, used to differentiate between events from
+ * different sensors within the same channel.
+ */
+ void onDirectChannelConfigured(int channelHandle, in VirtualSensor sensor, int rateLevel,
+ int reportToken);
}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
index e097189..f7af283 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
@@ -17,8 +17,13 @@
package android.companion.virtual.sensor;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.os.MemoryFile;
+import android.os.SharedMemory;
import java.time.Duration;
@@ -50,4 +55,74 @@
*/
void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
@NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency);
+
+ /**
+ * Called when a {@link android.hardware.SensorDirectChannel} is created.
+ *
+ * <p>The {@link android.hardware.SensorManager} instance used to create the direct channel must
+ * be associated with the virtual device.
+ *
+ * <p>A typical order of callback invocations is:
+ * <ul>
+ * <li>{@code onDirectChannelCreated} - the channel handle and the associated shared memory
+ * should be stored by the virtual device</li>
+ * <li>{@code onDirectChannelConfigured} with a positive {@code rateLevel} - the virtual
+ * device should start writing to the shared memory for the associated channel with the
+ * requested parameters.</li>
+ * <li>{@code onDirectChannelConfigured} with a {@code rateLevel = RATE_STOP} - the virtual
+ * device should stop writing to the shared memory for the associated channel.</li>
+ * <li>{@code onDirectChannelDestroyed} - the shared memory associated with the channel
+ * handle should be closed.</li>
+ * </ul>
+ *
+ * @param channelHandle Identifier of the newly created channel.
+ * @param sharedMemory writable shared memory region.
+ *
+ * @see android.hardware.SensorManager#createDirectChannel(MemoryFile)
+ * @see #onDirectChannelConfigured
+ * @see #onDirectChannelDestroyed
+ */
+ default void onDirectChannelCreated(@IntRange(from = 1) int channelHandle,
+ @NonNull SharedMemory sharedMemory) {}
+
+ /**
+ * Called when a {@link android.hardware.SensorDirectChannel} is destroyed.
+ *
+ * <p>The virtual device must perform any clean-up and close the shared memory that was
+ * received with the {@link #onDirectChannelCreated} callback and the corresponding
+ * {@code channelHandle}.
+ *
+ * @param channelHandle Identifier of the channel that was destroyed.
+ *
+ * @see SensorDirectChannel#close()
+ */
+ default void onDirectChannelDestroyed(@IntRange(from = 1) int channelHandle) {}
+
+ /**
+ * Called when a {@link android.hardware.SensorDirectChannel} is configured.
+ *
+ * <p>Sensor events for the corresponding sensor should be written at the indicated rate to the
+ * shared memory region that was received with the {@link #onDirectChannelCreated} callback and
+ * the corresponding {@code channelHandle}. The events should be written in the correct format
+ * and with the provided {@code reportToken} until the channel is reconfigured with
+ * {@link SensorDirectChannel#RATE_STOP}.
+ *
+ * <p>The sensor must support direct channel in order for this callback to be invoked. Only
+ * {@link MemoryFile} sensor direct channels are supported for virtual sensors.
+ *
+ * @param channelHandle Identifier of the channel that was configured.
+ * @param sensor The sensor, for which the channel was configured.
+ * @param rateLevel The rate level used to configure the direct sensor channel.
+ * @param reportToken A positive sensor report token, used to differentiate between events from
+ * different sensors within the same channel.
+ *
+ * @see VirtualSensorConfig.Builder#setHighestDirectReportRateLevel(int)
+ * @see VirtualSensorConfig.Builder#setDirectChannelTypesSupported(int)
+ * @see android.hardware.SensorManager#createDirectChannel(MemoryFile)
+ * @see #onDirectChannelCreated
+ * @see SensorDirectChannel#configure(Sensor, int)
+ */
+ default void onDirectChannelConfigured(@IntRange(from = 1) int channelHandle,
+ @NonNull VirtualSensor sensor, @SensorDirectChannel.RateLevel int rateLevel,
+ @IntRange(from = 1) int reportToken) {}
}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 6d45365..ffbdff8 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -21,11 +21,13 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
+
/**
* Configuration for creation of a virtual sensor.
* @see VirtualSensor
@@ -33,6 +35,14 @@
*/
@SystemApi
public final class VirtualSensorConfig implements Parcelable {
+ private static final String TAG = "VirtualSensorConfig";
+
+ // Mask for direct mode highest rate level, bit 7, 8, 9.
+ private static final int DIRECT_REPORT_MASK = 0x380;
+ private static final int DIRECT_REPORT_SHIFT = 7;
+
+ // Mask for supported direct channel, bit 10, 11
+ private static final int DIRECT_CHANNEL_SHIFT = 10;
private final int mType;
@NonNull
@@ -40,16 +50,21 @@
@Nullable
private final String mVendor;
- private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor) {
+ private final int mFlags;
+
+ private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+ int flags) {
mType = type;
mName = name;
mVendor = vendor;
+ mFlags = flags;
}
private VirtualSensorConfig(@NonNull Parcel parcel) {
mType = parcel.readInt();
mName = parcel.readString8();
mVendor = parcel.readString8();
+ mFlags = parcel.readInt();
}
@Override
@@ -62,6 +77,7 @@
parcel.writeInt(mType);
parcel.writeString8(mName);
parcel.writeString8(mVendor);
+ parcel.writeInt(mFlags);
}
/**
@@ -92,22 +108,64 @@
}
/**
+ * Returns the highest supported direct report mode rate level of the sensor.
+ *
+ * @see Sensor#getHighestDirectReportRateLevel()
+ */
+ @SensorDirectChannel.RateLevel
+ public int getHighestDirectReportRateLevel() {
+ int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT);
+ return rateLevel <= SensorDirectChannel.RATE_VERY_FAST
+ ? rateLevel : SensorDirectChannel.RATE_VERY_FAST;
+ }
+
+ /**
+ * Returns a combination of all supported direct channel types.
+ *
+ * @see Builder#setDirectChannelTypesSupported(int)
+ * @see Sensor#isDirectChannelTypeSupported(int)
+ */
+ public @SensorDirectChannel.MemoryType int getDirectChannelTypesSupported() {
+ int memoryTypes = 0;
+ if ((mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0) {
+ memoryTypes |= SensorDirectChannel.TYPE_MEMORY_FILE;
+ }
+ if ((mFlags & (1 << (DIRECT_CHANNEL_SHIFT + 1))) > 0) {
+ memoryTypes |= SensorDirectChannel.TYPE_HARDWARE_BUFFER;
+ }
+ return memoryTypes;
+ }
+
+ /**
+ * Returns the sensor flags.
+ * @hide
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
* Builder for {@link VirtualSensorConfig}.
*/
public static final class Builder {
+ private static final int FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED =
+ 1 << DIRECT_CHANNEL_SHIFT;
private final int mType;
@NonNull
private final String mName;
@Nullable
private String mVendor;
+ private int mFlags;
+ @SensorDirectChannel.RateLevel
+ int mHighestDirectReportRateLevel;
/**
* Creates a new builder.
*
* @param type The type of the sensor, matching {@link Sensor#getType}.
* @param name The name of the sensor. Must be unique among all sensors with the same type
- * that belong to the same virtual device.
+ * that belong to the same virtual device.
*/
public Builder(int type, @NonNull String name) {
mType = type;
@@ -119,7 +177,19 @@
*/
@NonNull
public VirtualSensorConfig build() {
- return new VirtualSensorConfig(mType, mName, mVendor);
+ if (mHighestDirectReportRateLevel > 0) {
+ if ((mFlags & FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED) == 0) {
+ throw new IllegalArgumentException("Setting direct channel type is required "
+ + "for sensors with direct channel support.");
+ }
+ mFlags |= mHighestDirectReportRateLevel << DIRECT_REPORT_SHIFT;
+ }
+ if ((mFlags & FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED) > 0
+ && mHighestDirectReportRateLevel == 0) {
+ throw new IllegalArgumentException("Highest direct report rate level is "
+ + "required for sensors with direct channel support.");
+ }
+ return new VirtualSensorConfig(mType, mName, mVendor, mFlags);
}
/**
@@ -130,6 +200,44 @@
mVendor = vendor;
return this;
}
+
+ /**
+ * Sets the highest supported rate level for direct sensor report.
+ *
+ * @see VirtualSensorConfig#getHighestDirectReportRateLevel()
+ */
+ @NonNull
+ public VirtualSensorConfig.Builder setHighestDirectReportRateLevel(
+ @SensorDirectChannel.RateLevel int rateLevel) {
+ mHighestDirectReportRateLevel = rateLevel;
+ return this;
+ }
+
+ /**
+ * Sets whether direct sensor channel of the given types is supported.
+ *
+ * @param memoryTypes A combination of {@link SensorDirectChannel.MemoryType} flags
+ * indicating the types of shared memory supported for creating direct channels. Only
+ * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for virtual
+ * sensors.
+ * @throws IllegalArgumentException if {@link SensorDirectChannel#TYPE_HARDWARE_BUFFER} is
+ * set to be supported.
+ */
+ @NonNull
+ public VirtualSensorConfig.Builder setDirectChannelTypesSupported(
+ @SensorDirectChannel.MemoryType int memoryTypes) {
+ if ((memoryTypes & SensorDirectChannel.TYPE_MEMORY_FILE) > 0) {
+ mFlags |= FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED;
+ } else {
+ mFlags &= ~FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED;
+ }
+ if ((memoryTypes & ~SensorDirectChannel.TYPE_MEMORY_FILE) > 0) {
+ throw new IllegalArgumentException(
+ "Only TYPE_MEMORY_FILE direct channels can be supported for virtual "
+ + "sensors.");
+ }
+ return this;
+ }
}
@NonNull
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 795c77f..d3502c5 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -389,6 +389,8 @@
@Override
public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
final Bundle result = new Bundle();
try {
result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri));
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index ffdc7b38..6b09c30 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.MathUtils;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -98,22 +99,81 @@
public static FontScaleConverter forScale(float fontScale) {
if (fontScale <= 1) {
// We don't need non-linear curves for shrinking text or for 100%.
- // Also, fontScale==0 should not have a curve either
+ // Also, fontScale==0 should not have a curve either.
+ // And ignore negative font scales; that's just silly.
return null;
}
FontScaleConverter lookupTable = get(fontScale);
- // TODO(b/247861716): interpolate between two tables when null
+ if (lookupTable != null) {
+ return lookupTable;
+ }
- return lookupTable;
+ // Didn't find an exact match: interpolate between two existing tables
+ final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale));
+ if (index >= 0) {
+ // This should never happen, should have been covered by get() above.
+ return LOOKUP_TABLES.valueAt(index);
+ }
+ // Didn't find an exact match: interpolate between two existing tables
+ final int lowerIndex = -(index + 1) - 1;
+ final int higherIndex = lowerIndex + 1;
+ if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) {
+ // We have gone beyond our bounds and have nothing to interpolate between. Just give
+ // them a straight linear table instead.
+ // This works because when FontScaleConverter encounters a size beyond its bounds, it
+ // calculates a linear fontScale factor using the ratio of the last element pair.
+ return new FontScaleConverter(new float[] {1f}, new float[] {fontScale});
+ } else {
+ float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex));
+ float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex));
+ float interpolationPoint = MathUtils.constrainedMap(
+ /* rangeMin= */ 0f,
+ /* rangeMax= */ 1f,
+ startScale,
+ endScale,
+ fontScale
+ );
+ return createInterpolatedTableBetween(
+ LOOKUP_TABLES.valueAt(lowerIndex),
+ LOOKUP_TABLES.valueAt(higherIndex),
+ interpolationPoint);
+ }
+ }
+
+ @NonNull
+ private static FontScaleConverter createInterpolatedTableBetween(
+ FontScaleConverter start,
+ FontScaleConverter end,
+ float interpolationPoint
+ ) {
+ float[] commonSpSizes = new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100f};
+ float[] dpInterpolated = new float[commonSpSizes.length];
+
+ for (int i = 0; i < commonSpSizes.length; i++) {
+ float sp = commonSpSizes[i];
+ float startDp = start.convertSpToDp(sp);
+ float endDp = end.convertSpToDp(sp);
+ dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint);
+ }
+
+ return new FontScaleConverter(commonSpSizes, dpInterpolated);
+ }
+
+ private static int getKey(float fontScale) {
+ return (int) (fontScale * SCALE_KEY_MULTIPLIER);
+ }
+
+ private static float getScaleFromKey(int key) {
+ return (float) key / SCALE_KEY_MULTIPLIER;
}
private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
- LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter);
+ LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter);
}
@Nullable
private static FontScaleConverter get(float scaleKey) {
- return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER));
+ return LOOKUP_TABLES.get(getKey(scaleKey));
}
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1a3e0b0..73157e6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -92,7 +92,8 @@
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
private static native int nativeCreateDirectChannel(
- long nativeInstance, long size, int channelType, int fd, HardwareBuffer buffer);
+ long nativeInstance, int deviceId, long size, int channelType, int fd,
+ HardwareBuffer buffer);
private static native void nativeDestroyDirectChannel(
long nativeInstance, int channelHandle);
private static native int nativeConfigDirectChannel(
@@ -695,6 +696,10 @@
/** @hide */
protected SensorDirectChannel createDirectChannelImpl(
MemoryFile memoryFile, HardwareBuffer hardwareBuffer) {
+ int deviceId = mContext.getDeviceId();
+ if (isDeviceSensorPolicyDefault(deviceId)) {
+ deviceId = DEVICE_ID_DEFAULT;
+ }
int id;
int type;
long size;
@@ -713,8 +718,8 @@
}
size = memoryFile.length();
- id = nativeCreateDirectChannel(
- mNativeInstance, size, SensorDirectChannel.TYPE_MEMORY_FILE, fd, null);
+ id = nativeCreateDirectChannel(mNativeInstance, deviceId, size,
+ SensorDirectChannel.TYPE_MEMORY_FILE, fd, null);
if (id <= 0) {
throw new UncheckedIOException(
new IOException("create MemoryFile direct channel failed " + id));
@@ -738,7 +743,7 @@
}
size = hardwareBuffer.getWidth();
id = nativeCreateDirectChannel(
- mNativeInstance, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+ mNativeInstance, deviceId, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-1, hardwareBuffer);
if (id <= 0) {
throw new UncheckedIOException(
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b766cd1..50dd7a0 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -989,24 +989,6 @@
/**
* Creates a virtual display.
- *
- * @see #createVirtualDisplay(String, int, int, int, float, Surface, int,
- * Handler, VirtualDisplay.Callback)
- */
- @Nullable
- public VirtualDisplay createVirtualDisplay(@NonNull String name,
- @IntRange(from = 1) int width,
- @IntRange(from = 1) int height,
- @IntRange(from = 1) int densityDpi,
- float requestedRefreshRate,
- @Nullable Surface surface,
- @VirtualDisplayFlag int flags) {
- return createVirtualDisplay(name, width, height, densityDpi, requestedRefreshRate,
- surface, flags, null, null);
- }
-
- /**
- * Creates a virtual display.
* <p>
* The content of a virtual display is rendered to a {@link Surface} provided
* by the application.
@@ -1056,8 +1038,23 @@
@VirtualDisplayFlag int flags,
@Nullable VirtualDisplay.Callback callback,
@Nullable Handler handler) {
- return createVirtualDisplay(name, width, height, densityDpi, 0.0f, surface,
- flags, handler, callback);
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(name, width, height, densityDpi);
+ builder.setFlags(flags);
+ if (surface != null) {
+ builder.setSurface(surface);
+ }
+ return createVirtualDisplay(builder.build(), handler, callback);
+ }
+
+ /**
+ * Creates a virtual display.
+ *
+ * @see #createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
+ */
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig config) {
+ return createVirtualDisplay(config, /*handler=*/null, /*callback=*/null);
}
/**
@@ -1084,21 +1081,7 @@
* turning off the screen.
* </p>
*
- * @param name The name of the virtual display, must be non-empty.
- * @param width The width of the virtual display in pixels, must be greater than 0.
- * @param height The height of the virtual display in pixels, must be greater than 0.
- * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
- * @param requestedRefreshRate The requested refresh rate in frames per second.
- * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
- * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
- * up or down to a divisor of the physical display. If 0 is specified, the virtual
- * display is refreshed at the physical display refresh rate.
- * @param surface The surface to which the content of the virtual display should
- * be rendered, or null if there is none initially.
- * @param flags A combination of virtual display flags:
- * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
- * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
- * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+ * @param config The configuration of the virtual display, must be non-null.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
* @param callback Callback to call when the state of the {@link VirtualDisplay} changes
@@ -1106,33 +1089,14 @@
* not create the virtual display.
*
* @throws SecurityException if the caller does not have permission to create
- * a virtual display with the specified flags.
+ * a virtual display with flags specified in the configuration.
*/
@Nullable
- public VirtualDisplay createVirtualDisplay(@NonNull String name,
- @IntRange(from = 1) int width,
- @IntRange(from = 1) int height,
- @IntRange(from = 1) int densityDpi,
- float requestedRefreshRate,
- @Nullable Surface surface,
- @VirtualDisplayFlag int flags,
+ public VirtualDisplay createVirtualDisplay(
+ @NonNull VirtualDisplayConfig config,
@Nullable Handler handler,
@Nullable VirtualDisplay.Callback callback) {
- if (!ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE && requestedRefreshRate != 0.0f) {
- Slog.e(TAG, "Please turn on ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE to use the new api");
- return null;
- }
-
- final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
- height, densityDpi);
- builder.setFlags(flags);
- if (surface != null) {
- builder.setSurface(surface);
- }
- if (requestedRefreshRate != 0.0f) {
- builder.setRequestedRefreshRate(requestedRefreshRate);
- }
- return createVirtualDisplay(null /* projection */, builder.build(), callback, handler,
+ return createVirtualDisplay(null /* projection */, config, callback, handler,
null /* windowContext */);
}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index f6a2e33..6b56a06 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -18,121 +18,44 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.media.projection.MediaProjection;
+import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.Display;
import android.view.Surface;
-import com.android.internal.util.DataClass;
-
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
- * Holds configuration used to create {@link VirtualDisplay} instances. See
- * {@link MediaProjection#createVirtualDisplay} and
- * {@link android.companion.virtual.VirtualDeviceManager.VirtualDevice#createVirtualDisplay}.
+ * Holds configuration used to create {@link VirtualDisplay} instances.
*
- * @hide
+ * @see DisplayManager#createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
+ * @see MediaProjection#createVirtualDisplay
*/
-@DataClass(genParcelable = true, genAidl = true, genBuilder = true)
public final class VirtualDisplayConfig implements Parcelable {
- /**
- * The name of the virtual display, must be non-empty.
- */
- @NonNull
- private String mName;
- /**
- * The width of the virtual display in pixels. Must be greater than 0.
- */
- @IntRange(from = 1)
- private int mWidth;
+ private final String mName;
+ private final int mWidth;
+ private final int mHeight;
+ private final int mDensityDpi;
+ private final int mFlags;
+ private final Surface mSurface;
+ private final String mUniqueId;
+ private final int mDisplayIdToMirror;
+ private final boolean mWindowManagerMirroring;
+ private ArrayList<String> mDisplayCategories = null;
+ private final float mRequestedRefreshRate;
- /**
- * The height of the virtual display in pixels. Must be greater than 0.
- */
- @IntRange(from = 1)
- private int mHeight;
-
- /**
- * The density of the virtual display in dpi. Must be greater than 0.
- */
- @IntRange(from = 1)
- private int mDensityDpi;
-
- /**
- * A combination of virtual display flags.
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
- * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
- */
- @VirtualDisplayFlag
- private int mFlags = 0;
-
- /**
- * The surface to which the content of the virtual display should be rendered, or null if
- * there is none initially.
- */
- @Nullable
- private Surface mSurface = null;
-
- /**
- * The unique identifier for the display. Shouldn't be displayed to the user.
- * @hide
- */
- @Nullable
- private String mUniqueId = null;
-
- /**
- * The id of the display that the virtual display should mirror, or
- * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
- */
- private int mDisplayIdToMirror = DEFAULT_DISPLAY;
-
- /**
- * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
- * if DisplayManager should record contents instead.
- */
- private boolean mWindowManagerMirroring = false;
-
- /**
- * The display categories. If set, only corresponding activities from the same category can be
- * shown on the display.
- */
- @DataClass.PluralOf("displayCategory")
- @NonNull private List<String> mDisplayCategories = new ArrayList<>();
-
- /**
- * The refresh rate of a virtual display in frames per second.
- * If this value is non-zero, this is the requested refresh rate to set.
- * If this value is zero, the system chooses a default refresh rate.
- */
- private float mRequestedRefreshRate = 0.0f;
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- /* package-private */ VirtualDisplayConfig(
+ private VirtualDisplayConfig(
@NonNull String name,
@IntRange(from = 1) int width,
@IntRange(from = 1) int height,
@@ -142,219 +65,200 @@
@Nullable String uniqueId,
int displayIdToMirror,
boolean windowManagerMirroring,
- @NonNull List<String> displayCategories,
+ @NonNull ArrayList<String> displayCategories,
float requestedRefreshRate) {
- this.mName = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mName);
- this.mWidth = width;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mWidth,
- "from", 1);
- this.mHeight = height;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mHeight,
- "from", 1);
- this.mDensityDpi = densityDpi;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mDensityDpi,
- "from", 1);
- this.mFlags = flags;
- com.android.internal.util.AnnotationValidations.validate(
- VirtualDisplayFlag.class, null, mFlags);
- this.mSurface = surface;
- this.mUniqueId = uniqueId;
- this.mDisplayIdToMirror = displayIdToMirror;
- this.mWindowManagerMirroring = windowManagerMirroring;
- this.mDisplayCategories = displayCategories;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mDisplayCategories);
- this.mRequestedRefreshRate = requestedRefreshRate;
-
- // onConstructed(); // You can define this method to get a callback
+ mName = name;
+ mWidth = width;
+ mHeight = height;
+ mDensityDpi = densityDpi;
+ mFlags = flags;
+ mSurface = surface;
+ mUniqueId = uniqueId;
+ mDisplayIdToMirror = displayIdToMirror;
+ mWindowManagerMirroring = windowManagerMirroring;
+ mDisplayCategories = displayCategories;
+ mRequestedRefreshRate = requestedRefreshRate;
}
/**
- * The name of the virtual display, must be non-empty.
+ * Returns the name of the virtual display.
*/
- @DataClass.Generated.Member
- public @NonNull String getName() {
+ @NonNull
+ public String getName() {
return mName;
}
/**
- * The width of the virtual display in pixels. Must be greater than 0.
+ * Returns the width of the virtual display in pixels.
*/
- @DataClass.Generated.Member
- public @IntRange(from = 1) int getWidth() {
+ public int getWidth() {
return mWidth;
}
/**
- * The height of the virtual display in pixels. Must be greater than 0.
+ * Returns the height of the virtual display in pixels.
*/
- @DataClass.Generated.Member
- public @IntRange(from = 1) int getHeight() {
+ public int getHeight() {
return mHeight;
}
/**
- * The density of the virtual display in dpi. Must be greater than 0.
+ * Returns the density of the virtual display in dpi.
*/
- @DataClass.Generated.Member
- public @IntRange(from = 1) int getDensityDpi() {
+ public int getDensityDpi() {
return mDensityDpi;
}
/**
- * A combination of virtual display flags.
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
- * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+ * Returns the virtual display flags.
+ *
+ * @see Builder#setFlags
*/
- @DataClass.Generated.Member
- public @VirtualDisplayFlag int getFlags() {
+ public int getFlags() {
return mFlags;
}
/**
- * The surface to which the content of the virtual display should be rendered, or null if
- * there is none initially.
+ * Returns the surface to which the content of the virtual display should be rendered, if any.
+ *
+ * @see Builder#setSurface
*/
- @DataClass.Generated.Member
- public @Nullable Surface getSurface() {
+ @Nullable
+ public Surface getSurface() {
return mSurface;
}
/**
- * The unique identifier for the display. Shouldn't be displayed to the user.
- *
+ * Returns the unique identifier for the display. Shouldn't be displayed to the user.
* @hide
*/
- @DataClass.Generated.Member
- public @Nullable String getUniqueId() {
+ @Nullable
+ public String getUniqueId() {
return mUniqueId;
}
/**
- * The id of the display that the virtual display should mirror, or
- * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+ * Returns the id of the display that the virtual display should mirror, or
+ * {@link android.view.Display#DEFAULT_DISPLAY} if there is none.
+ * @hide
*/
- @DataClass.Generated.Member
public int getDisplayIdToMirror() {
return mDisplayIdToMirror;
}
/**
- * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
+ * Whether if WindowManager is responsible for mirroring content to this VirtualDisplay, or
* if DisplayManager should record contents instead.
+ * @hide
*/
- @DataClass.Generated.Member
public boolean isWindowManagerMirroring() {
return mWindowManagerMirroring;
}
/**
- * The display categories. If set, only corresponding activities from the same category can be
- * shown on the display.
+ * Returns the display categories.
+ *
+ * @see Builder#setDisplayCategories
*/
- @DataClass.Generated.Member
- public @NonNull List<String> getDisplayCategories() {
- return mDisplayCategories;
+ @NonNull
+ public List<String> getDisplayCategories() {
+ return Collections.unmodifiableList(mDisplayCategories);
}
/**
- * The refresh rate of a virtual display in frames per second.
- * If this value is none zero, this is the requested refresh rate to set.
- * If this value is zero, the system chooses a default refresh rate.
+ * Returns the refresh rate of a virtual display in frames per second, or zero if it is using a
+ * default refresh rate chosen by the system.
+ *
+ * @see Builder#setRequestedRefreshRate
*/
- @DataClass.Generated.Member
public float getRequestedRefreshRate() {
return mRequestedRefreshRate;
}
@Override
- @DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- int flg = 0;
- if (mWindowManagerMirroring) flg |= 0x100;
- if (mSurface != null) flg |= 0x20;
- if (mUniqueId != null) flg |= 0x40;
- dest.writeInt(flg);
- dest.writeString(mName);
+ dest.writeString8(mName);
dest.writeInt(mWidth);
dest.writeInt(mHeight);
dest.writeInt(mDensityDpi);
dest.writeInt(mFlags);
- if (mSurface != null) dest.writeTypedObject(mSurface, flags);
- if (mUniqueId != null) dest.writeString(mUniqueId);
+ dest.writeTypedObject(mSurface, flags);
+ dest.writeString8(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
+ dest.writeBoolean(mWindowManagerMirroring);
dest.writeStringList(mDisplayCategories);
dest.writeFloat(mRequestedRefreshRate);
}
@Override
- @DataClass.Generated.Member
public int describeContents() { return 0; }
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ VirtualDisplayConfig(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- int flg = in.readInt();
- boolean windowManagerMirroring = (flg & 0x100) != 0;
- String name = in.readString();
- int width = in.readInt();
- int height = in.readInt();
- int densityDpi = in.readInt();
- int flags = in.readInt();
- Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
- String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
- int displayIdToMirror = in.readInt();
- List<String> displayCategories = new ArrayList<>();
- in.readStringList(displayCategories);
- float requestedRefreshRate = in.readFloat();
-
- this.mName = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mName);
- this.mWidth = width;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mWidth,
- "from", 1);
- this.mHeight = height;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mHeight,
- "from", 1);
- this.mDensityDpi = densityDpi;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mDensityDpi,
- "from", 1);
- this.mFlags = flags;
- com.android.internal.util.AnnotationValidations.validate(
- VirtualDisplayFlag.class, null, mFlags);
- this.mSurface = surface;
- this.mUniqueId = uniqueId;
- this.mDisplayIdToMirror = displayIdToMirror;
- this.mWindowManagerMirroring = windowManagerMirroring;
- this.mDisplayCategories = displayCategories;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mDisplayCategories);
- this.mRequestedRefreshRate = requestedRefreshRate;
-
- // onConstructed(); // You can define this method to get a callback
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VirtualDisplayConfig)) {
+ return false;
+ }
+ VirtualDisplayConfig that = (VirtualDisplayConfig) o;
+ return Objects.equals(mName, that.mName)
+ && mWidth == that.mWidth
+ && mHeight == that.mHeight
+ && mDensityDpi == that.mDensityDpi
+ && mFlags == that.mFlags
+ && Objects.equals(mSurface, that.mSurface)
+ && Objects.equals(mUniqueId, that.mUniqueId)
+ && mDisplayIdToMirror == that.mDisplayIdToMirror
+ && mWindowManagerMirroring == that.mWindowManagerMirroring
+ && Objects.equals(mDisplayCategories, that.mDisplayCategories)
+ && mRequestedRefreshRate == that.mRequestedRefreshRate;
}
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<VirtualDisplayConfig> CREATOR
+ @Override
+ public int hashCode() {
+ int hashCode = Objects.hash(
+ mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
+ mDisplayIdToMirror, mWindowManagerMirroring, mDisplayCategories,
+ mRequestedRefreshRate);
+ return hashCode;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "VirtualDisplayConfig("
+ + " mName=" + mName
+ + " mHeight=" + mHeight
+ + " mWidth=" + mWidth
+ + " mDensityDpi=" + mDensityDpi
+ + " mFlags=" + mFlags
+ + " mSurface=" + mSurface
+ + " mUniqueId=" + mUniqueId
+ + " mDisplayIdToMirror=" + mDisplayIdToMirror
+ + " mWindowManagerMirroring=" + mWindowManagerMirroring
+ + " mDisplayCategories=" + mDisplayCategories
+ + " mRequestedRefreshRate=" + mRequestedRefreshRate
+ + ")";
+ }
+
+ private VirtualDisplayConfig(@NonNull Parcel in) {
+ mName = in.readString8();
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mDensityDpi = in.readInt();
+ mFlags = in.readInt();
+ mSurface = in.readTypedObject(Surface.CREATOR);
+ mUniqueId = in.readString8();
+ mDisplayIdToMirror = in.readInt();
+ mWindowManagerMirroring = in.readBoolean();
+ mDisplayCategories = new ArrayList<>();
+ in.readStringList(mDisplayCategories);
+ mRequestedRefreshRate = in.readFloat();
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<VirtualDisplayConfig> CREATOR
= new Parcelable.Creator<VirtualDisplayConfig>() {
@Override
public VirtualDisplayConfig[] newArray(int size) {
@@ -368,229 +272,161 @@
};
/**
- * A builder for {@link VirtualDisplayConfig}
+ * A builder for {@link VirtualDisplayConfig}.
*/
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
public static final class Builder {
-
- private @NonNull String mName;
- private @IntRange(from = 1) int mWidth;
- private @IntRange(from = 1) int mHeight;
- private @IntRange(from = 1) int mDensityDpi;
- private @VirtualDisplayFlag int mFlags;
- private @Nullable Surface mSurface;
- private @Nullable String mUniqueId;
- private int mDisplayIdToMirror;
- private boolean mWindowManagerMirroring;
- private @NonNull List<String> mDisplayCategories;
- private float mRequestedRefreshRate;
-
- private long mBuilderFieldsSet = 0L;
+ private final String mName;
+ private final int mWidth;
+ private final int mHeight;
+ private final int mDensityDpi;
+ private int mFlags = 0;
+ private Surface mSurface = null;
+ private String mUniqueId = null;
+ private int mDisplayIdToMirror = DEFAULT_DISPLAY;
+ private boolean mWindowManagerMirroring = false;
+ private ArrayList<String> mDisplayCategories = new ArrayList<>();
+ private float mRequestedRefreshRate = 0.0f;
/**
* Creates a new Builder.
*
- * @param name
- * The name of the virtual display, must be non-empty.
- * @param width
- * The width of the virtual display in pixels. Must be greater than 0.
- * @param height
- * The height of the virtual display in pixels. Must be greater than 0.
- * @param densityDpi
- * The density of the virtual display in dpi. Must be greater than 0.
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels. Must be greater than 0.
+ * @param height The height of the virtual display in pixels. Must be greater than 0.
+ * @param densityDpi The density of the virtual display in dpi. Must be greater than 0.
*/
public Builder(
@NonNull String name,
@IntRange(from = 1) int width,
@IntRange(from = 1) int height,
@IntRange(from = 1) int densityDpi) {
+ if (name == null) {
+ throw new IllegalArgumentException("Virtual display name is required");
+ }
+ if (width <= 0) {
+ throw new IllegalArgumentException("Virtual display width must be positive");
+ }
+ if (height <= 0) {
+ throw new IllegalArgumentException("Virtual display height must be positive");
+ }
+ if (densityDpi <= 0) {
+ throw new IllegalArgumentException("Virtual display density must be positive");
+ }
mName = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mName);
mWidth = width;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mWidth,
- "from", 1);
mHeight = height;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mHeight,
- "from", 1);
mDensityDpi = densityDpi;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mDensityDpi,
- "from", 1);
}
/**
- * The name of the virtual display, must be non-empty.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setName(@NonNull String value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mName = value;
- return this;
- }
-
- /**
- * The width of the virtual display in pixels. Must be greater than 0.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setWidth(@IntRange(from = 1) int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2;
- mWidth = value;
- return this;
- }
-
- /**
- * The height of the virtual display in pixels. Must be greater than 0.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setHeight(@IntRange(from = 1) int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4;
- mHeight = value;
- return this;
- }
-
- /**
- * The density of the virtual display in dpi. Must be greater than 0.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setDensityDpi(@IntRange(from = 1) int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x8;
- mDensityDpi = value;
- return this;
- }
-
- /**
- * A combination of virtual display flags.
+ * Sets the virtual display flags, a combination of
* {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
* {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
* {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
* {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
* or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
*/
- @DataClass.Generated.Member
- public @NonNull Builder setFlags(@VirtualDisplayFlag int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x10;
- mFlags = value;
+ @NonNull
+ public Builder setFlags(@VirtualDisplayFlag int flags) {
+ mFlags = flags;
return this;
}
/**
- * The surface to which the content of the virtual display should be rendered, or null if
- * there is none initially.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setSurface(@NonNull Surface value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x20;
- mSurface = value;
- return this;
- }
-
- /**
- * The unique identifier for the display. Shouldn't be displayed to the user.
+ * Sets the surface to which the content of the virtual display should be rendered.
*
+ * <p>The surface can also be set after the display creation using
+ * {@link VirtualDisplay#setSurface(Surface)}.
+ */
+ @NonNull
+ public Builder setSurface(@Nullable Surface surface) {
+ mSurface = surface;
+ return this;
+ }
+
+ /**
+ * Sets the unique identifier for the display.
* @hide
*/
- @DataClass.Generated.Member
- public @NonNull Builder setUniqueId(@NonNull String value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x40;
- mUniqueId = value;
+ @NonNull
+ public Builder setUniqueId(@Nullable String uniqueId) {
+ mUniqueId = uniqueId;
return this;
}
/**
- * The id of the display that the virtual display should mirror, or
- * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+ * Sets the id of the display that the virtual display should mirror.
+ * @hide
*/
- @DataClass.Generated.Member
- public @NonNull Builder setDisplayIdToMirror(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x80;
- mDisplayIdToMirror = value;
+ @NonNull
+ public Builder setDisplayIdToMirror(int displayIdToMirror) {
+ mDisplayIdToMirror = displayIdToMirror;
return this;
}
/**
- * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
- * if DisplayManager should record contents instead.
+ * Sets whether WindowManager is responsible for mirroring content to this VirtualDisplay.
+ * If unset or false, DisplayManager should record contents instead.
+ * @hide
*/
- @DataClass.Generated.Member
- public @NonNull Builder setWindowManagerMirroring(boolean value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x100;
- mWindowManagerMirroring = value;
+ @NonNull
+ public Builder setWindowManagerMirroring(boolean windowManagerMirroring) {
+ mWindowManagerMirroring = windowManagerMirroring;
return this;
}
/**
- * The display categories. If set, only corresponding activities from the same category can be
- * shown on the display.
+ * Sets the display categories.
+ *
+ * <p>The categories of the display indicate the type of activities allowed to run on that
+ * display. Activities can declare a display category using
+ * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
*/
- @DataClass.Generated.Member
- public @NonNull Builder setDisplayCategories(@NonNull List<String> value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x200;
- mDisplayCategories = value;
- return this;
- }
-
- /** @see #setDisplayCategories */
- @DataClass.Generated.Member
- public @NonNull Builder addDisplayCategory(@NonNull String value) {
- if (mDisplayCategories == null) setDisplayCategories(new ArrayList<>());
- mDisplayCategories.add(value);
+ @NonNull
+ public Builder setDisplayCategories(@NonNull List<String> displayCategories) {
+ mDisplayCategories.clear();
+ mDisplayCategories.addAll(Objects.requireNonNull(displayCategories));
return this;
}
/**
- * The refresh rate of a virtual display in frames per second.
- * If this value is none zero, this is the requested refresh rate to set.
- * If this value is zero, the system chooses a default refresh rate.
+ * Adds a display category.
+ *
+ * @see #setDisplayCategories
*/
- @DataClass.Generated.Member
- public @NonNull Builder setRequestedRefreshRate(float value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x400;
- mRequestedRefreshRate = value;
+ @NonNull
+ public Builder addDisplayCategory(@NonNull String displayCategory) {
+ mDisplayCategories.add(Objects.requireNonNull(displayCategory));
return this;
}
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull VirtualDisplayConfig build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x800; // Mark builder used
+ /**
+ * Sets the refresh rate of a virtual display in frames per second.
+ *
+ * <p>For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+ * a 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded up
+ * down to a divisor of the physical display. If unset or zero, the virtual display will be
+ * refreshed at the physical display refresh rate.
+ *
+ * @see Display#getRefreshRate()
+ */
+ @NonNull
+ public Builder setRequestedRefreshRate(
+ @FloatRange(from = 0.0f) float requestedRefreshRate) {
+ if (requestedRefreshRate < 0.0f) {
+ throw new IllegalArgumentException(
+ "Virtual display requested refresh rate must be non-negative");
+ }
+ mRequestedRefreshRate = requestedRefreshRate;
+ return this;
+ }
- if ((mBuilderFieldsSet & 0x10) == 0) {
- mFlags = 0;
- }
- if ((mBuilderFieldsSet & 0x20) == 0) {
- mSurface = null;
- }
- if ((mBuilderFieldsSet & 0x40) == 0) {
- mUniqueId = null;
- }
- if ((mBuilderFieldsSet & 0x80) == 0) {
- mDisplayIdToMirror = DEFAULT_DISPLAY;
- }
- if ((mBuilderFieldsSet & 0x100) == 0) {
- mWindowManagerMirroring = false;
- }
- if ((mBuilderFieldsSet & 0x200) == 0) {
- mDisplayCategories = new ArrayList<>();
- }
- if ((mBuilderFieldsSet & 0x400) == 0) {
- mRequestedRefreshRate = 0.0f;
- }
- VirtualDisplayConfig o = new VirtualDisplayConfig(
+ /**
+ * Builds the {@link VirtualDisplayConfig} instance.
+ */
+ @NonNull
+ public VirtualDisplayConfig build() {
+ return new VirtualDisplayConfig(
mName,
mWidth,
mHeight,
@@ -602,27 +438,6 @@
mWindowManagerMirroring,
mDisplayCategories,
mRequestedRefreshRate);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x800) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
}
}
-
- @DataClass.Generated(
- time = 1671047069703L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nprivate float mRequestedRefreshRate\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54e4909..a3f8599 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -99,7 +99,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
-import com.android.internal.widget.ILockSettings;
import java.io.IOException;
import java.lang.annotation.ElementType;
@@ -116,6 +115,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* The Settings provider contains global system-level device preferences.
@@ -2978,19 +2978,22 @@
}
private static final class GenerationTracker {
- private final MemoryIntArray mArray;
- private final Runnable mErrorHandler;
+ @NonNull private final String mName;
+ @NonNull private final MemoryIntArray mArray;
+ @NonNull private final Consumer<String> mErrorHandler;
private final int mIndex;
private int mCurrentGeneration;
- public GenerationTracker(@NonNull MemoryIntArray array, int index,
- int generation, Runnable errorHandler) {
+ GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
+ int generation, Consumer<String> errorHandler) {
+ mName = name;
mArray = array;
mIndex = index;
mErrorHandler = errorHandler;
mCurrentGeneration = generation;
}
+ // This method also updates the obsolete generation code stored locally
public boolean isGenerationChanged() {
final int currentGeneration = readCurrentGeneration();
if (currentGeneration >= 0) {
@@ -3011,9 +3014,7 @@
return mArray.get(mIndex);
} catch (IOException e) {
Log.e(TAG, "Error getting current generation", e);
- if (mErrorHandler != null) {
- mErrorHandler.run();
- }
+ mErrorHandler.accept(mName);
}
return -1;
}
@@ -3023,9 +3024,6 @@
mArray.close();
} catch (IOException e) {
Log.e(TAG, "Error closing backing array", e);
- if (mErrorHandler != null) {
- mErrorHandler.run();
- }
}
}
}
@@ -3088,8 +3086,21 @@
private final ArraySet<String> mAllFields;
private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
+ // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
@GuardedBy("this")
- private GenerationTracker mGenerationTracker;
+ private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
+
+ private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
+ synchronized (NameValueCache.this) {
+ Log.e(TAG, "Error accessing generation tracker - removing");
+ final GenerationTracker tracker = mGenerationTrackers.get(name);
+ if (tracker != null) {
+ tracker.destroy();
+ mGenerationTrackers.remove(name);
+ }
+ mValues.remove(name);
+ }
+ };
<T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
@@ -3178,6 +3189,43 @@
@UnsupportedAppUsage
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
+ final boolean isSelf = (userHandle == UserHandle.myUserId());
+ int currentGeneration = -1;
+ boolean needsGenerationTracker = false;
+
+ if (isSelf) {
+ synchronized (NameValueCache.this) {
+ final GenerationTracker generationTracker = mGenerationTrackers.get(name);
+ if (generationTracker != null) {
+ if (generationTracker.isGenerationChanged()) {
+ if (DEBUG) {
+ Log.i(TAG, "Generation changed for setting:" + name
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " and user:" + userHandle);
+ }
+ mValues.remove(name);
+ } else if (mValues.containsKey(name)) {
+ if (DEBUG) {
+ Log.i(TAG, "Cache hit for setting:" + name);
+ }
+ return mValues.get(name);
+ }
+ currentGeneration = generationTracker.getCurrentGeneration();
+ } else {
+ needsGenerationTracker = true;
+ }
+ }
+ } else {
+ if (DEBUG || LOCAL_LOGV) {
+ Log.v(TAG, "get setting for user " + userHandle
+ + " by user " + UserHandle.myUserId() + " so skipping cache");
+ }
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Cache miss for setting:" + name + " for user:" + userHandle);
+ }
+
// Check if the target settings key is readable. Reject if the caller is not system and
// is trying to access a settings key defined in the Settings.Secure, Settings.System or
// Settings.Global and is not annotated as @Readable.
@@ -3211,31 +3259,6 @@
}
}
- final boolean isSelf = (userHandle == UserHandle.myUserId());
- int currentGeneration = -1;
- if (isSelf) {
- synchronized (NameValueCache.this) {
- if (mGenerationTracker != null) {
- if (mGenerationTracker.isGenerationChanged()) {
- if (DEBUG) {
- Log.i(TAG, "Generation changed for type:"
- + mUri.getPath() + " in package:"
- + cr.getPackageName() +" and user:" + userHandle);
- }
- mValues.clear();
- } else if (mValues.containsKey(name)) {
- return mValues.get(name);
- }
- if (mGenerationTracker != null) {
- currentGeneration = mGenerationTracker.getCurrentGeneration();
- }
- }
- }
- } else {
- if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
- + " by user " + UserHandle.myUserId() + " so skipping cache");
- }
-
IContentProvider cp = mProviderHolder.getProvider(cr);
// Try the fast path first, not using query(). If this
@@ -3244,24 +3267,17 @@
// interface.
if (mCallGetCommand != null) {
try {
- Bundle args = null;
+ Bundle args = new Bundle();
if (!isSelf) {
- args = new Bundle();
args.putInt(CALL_METHOD_USER_KEY, userHandle);
}
- boolean needsGenerationTracker = false;
- synchronized (NameValueCache.this) {
- if (isSelf && mGenerationTracker == null) {
- needsGenerationTracker = true;
- if (args == null) {
- args = new Bundle();
- }
- args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
- if (DEBUG) {
- Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
- + " in package:" + cr.getPackageName() +" and user:"
- + userHandle);
- }
+ if (needsGenerationTracker) {
+ args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+ if (DEBUG) {
+ Log.i(TAG, "Requested generation tracker for setting:" + name
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " and user:" + userHandle);
}
}
Bundle b;
@@ -3298,33 +3314,24 @@
final int generation = b.getInt(
CALL_METHOD_GENERATION_KEY, 0);
if (DEBUG) {
- Log.i(TAG, "Received generation tracker for type:"
- + mUri.getPath() + " in package:"
- + cr.getPackageName() + " and user:"
- + userHandle + " with index:" + index);
+ Log.i(TAG, "Received generation tracker for setting:"
+ + name
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " and user:" + userHandle
+ + " with index:" + index);
}
- if (mGenerationTracker != null) {
- mGenerationTracker.destroy();
- }
- mGenerationTracker = new GenerationTracker(array, index,
- generation, () -> {
- synchronized (NameValueCache.this) {
- Log.e(TAG, "Error accessing generation"
- + " tracker - removing");
- if (mGenerationTracker != null) {
- GenerationTracker generationTracker =
- mGenerationTracker;
- mGenerationTracker = null;
- generationTracker.destroy();
- mValues.clear();
- }
- }
- });
+ mGenerationTrackers.put(name, new GenerationTracker(name,
+ array, index, generation,
+ mGenerationTrackerErrorHandler));
currentGeneration = generation;
}
}
- if (mGenerationTracker != null && currentGeneration ==
- mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTrackers.get(name) != null && currentGeneration
+ == mGenerationTrackers.get(name).getCurrentGeneration()) {
+ if (DEBUG) {
+ Log.i(TAG, "Updating cache for setting:" + name);
+ }
mValues.put(name, value);
}
}
@@ -3367,15 +3374,14 @@
String value = c.moveToNext() ? c.getString(0) : null;
synchronized (NameValueCache.this) {
- if (mGenerationTracker != null
- && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTrackers.get(name) != null && currentGeneration
+ == mGenerationTrackers.get(name).getCurrentGeneration()) {
+ if (DEBUG) {
+ Log.i(TAG, "Updating cache for setting:" + name + " using query");
+ }
mValues.put(name, value);
}
}
- if (LOCAL_LOGV) {
- Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
- name + " = " + (value == null ? "(null)" : value));
- }
return value;
} catch (RemoteException e) {
Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
@@ -3409,18 +3415,29 @@
Config.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
+ boolean needsGenerationTracker = false;
synchronized (NameValueCache.this) {
- if (mGenerationTracker != null) {
- if (mGenerationTracker.isGenerationChanged()) {
+ final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
+ if (generationTracker != null) {
+ if (generationTracker.isGenerationChanged()) {
if (DEBUG) {
- Log.i(TAG, "Generation changed for type:" + mUri.getPath()
+ Log.i(TAG, "Generation changed for prefix:" + prefix
+ + " type:" + mUri.getPath()
+ " in package:" + cr.getPackageName());
}
- mValues.clear();
+ for (int i = 0; i < mValues.size(); ++i) {
+ String key = mValues.keyAt(i);
+ if (key.startsWith(prefix)) {
+ mValues.remove(key);
+ }
+ }
} else {
boolean prefixCached = mValues.containsKey(prefix);
if (prefixCached) {
+ if (DEBUG) {
+ Log.i(TAG, "Cache hit for prefix:" + prefix);
+ }
if (!names.isEmpty()) {
for (String name : names) {
if (mValues.containsKey(name)) {
@@ -3440,9 +3457,9 @@
return keyValues;
}
}
- if (mGenerationTracker != null) {
- currentGeneration = mGenerationTracker.getCurrentGeneration();
- }
+ currentGeneration = generationTracker.getCurrentGeneration();
+ } else {
+ needsGenerationTracker = true;
}
}
@@ -3450,20 +3467,20 @@
// No list command specified, return empty map
return keyValues;
}
+ if (DEBUG) {
+ Log.i(TAG, "Cache miss for prefix:" + prefix);
+ }
IContentProvider cp = mProviderHolder.getProvider(cr);
try {
Bundle args = new Bundle();
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
- boolean needsGenerationTracker = false;
- synchronized (NameValueCache.this) {
- if (mGenerationTracker == null) {
- needsGenerationTracker = true;
- args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
- if (DEBUG) {
- Log.i(TAG, "Requested generation tracker for type: "
- + mUri.getPath() + " in package:" + cr.getPackageName());
- }
+ if (needsGenerationTracker) {
+ args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+ if (DEBUG) {
+ Log.i(TAG, "Requested generation tracker for prefix:" + prefix
+ + " type: " + mUri.getPath()
+ + " in package:" + cr.getPackageName());
}
}
@@ -3516,32 +3533,22 @@
final int generation = b.getInt(
CALL_METHOD_GENERATION_KEY, 0);
if (DEBUG) {
- Log.i(TAG, "Received generation tracker for type:"
- + mUri.getPath() + " in package:"
- + cr.getPackageName() + " with index:" + index);
+ Log.i(TAG, "Received generation tracker for prefix:" + prefix
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " with index:" + index);
}
- if (mGenerationTracker != null) {
- mGenerationTracker.destroy();
- }
- mGenerationTracker = new GenerationTracker(array, index,
- generation, () -> {
- synchronized (NameValueCache.this) {
- Log.e(TAG, "Error accessing generation tracker"
- + " - removing");
- if (mGenerationTracker != null) {
- GenerationTracker generationTracker =
- mGenerationTracker;
- mGenerationTracker = null;
- generationTracker.destroy();
- mValues.clear();
- }
- }
- });
+ mGenerationTrackers.put(prefix,
+ new GenerationTracker(prefix, array, index, generation,
+ mGenerationTrackerErrorHandler));
currentGeneration = generation;
}
}
- if (mGenerationTracker != null && currentGeneration
- == mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTrackers.get(prefix) != null && currentGeneration
+ == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
+ if (DEBUG) {
+ Log.i(TAG, "Updating cache for prefix:" + prefix);
+ }
// cache the complete list of flags for the namespace
mValues.putAll(flagsToValues);
// Adding the prefix as a signal that the prefix is cached.
@@ -3557,11 +3564,11 @@
public void clearGenerationTrackerForTest() {
synchronized (NameValueCache.this) {
- if (mGenerationTracker != null) {
- mGenerationTracker.destroy();
+ for (int i = 0; i < mGenerationTrackers.size(); i++) {
+ mGenerationTrackers.valueAt(i).destroy();
}
+ mGenerationTrackers.clear();
mValues.clear();
- mGenerationTracker = null;
}
}
}
@@ -6184,9 +6191,6 @@
sProviderHolder,
Secure.class);
- private static ILockSettings sLockSettings = null;
-
- private static boolean sIsSystemProcess;
@UnsupportedAppUsage
private static final HashSet<String> MOVED_TO_LOCK_SETTINGS;
@UnsupportedAppUsage
@@ -6350,35 +6354,25 @@
return Global.getStringForUser(resolver, name, userHandle);
}
- if (MOVED_TO_LOCK_SETTINGS.contains(name)) {
- synchronized (Secure.class) {
- if (sLockSettings == null) {
- sLockSettings = ILockSettings.Stub.asInterface(
- (IBinder) ServiceManager.getService("lock_settings"));
- sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID;
- }
- }
- if (sLockSettings != null && !sIsSystemProcess) {
- // No context; use the ActivityThread's context as an approximation for
- // determining the target API level.
- Application application = ActivityThread.currentApplication();
+ if (MOVED_TO_LOCK_SETTINGS.contains(name) && Process.myUid() != Process.SYSTEM_UID) {
+ // No context; use the ActivityThread's context as an approximation for
+ // determining the target API level.
+ Application application = ActivityThread.currentApplication();
- boolean isPreMnc = application != null
- && application.getApplicationInfo() != null
- && application.getApplicationInfo().targetSdkVersion
- <= VERSION_CODES.LOLLIPOP_MR1;
- if (isPreMnc) {
- try {
- return sLockSettings.getString(name, "0", userHandle);
- } catch (RemoteException re) {
- // Fall through
- }
- } else {
- throw new SecurityException("Settings.Secure." + name
- + " is deprecated and no longer accessible."
- + " See API documentation for potential replacements.");
- }
+ boolean isPreMnc = application != null
+ && application.getApplicationInfo() != null
+ && application.getApplicationInfo().targetSdkVersion
+ <= VERSION_CODES.LOLLIPOP_MR1;
+ if (isPreMnc) {
+ // Old apps used to get the three deprecated LOCK_PATTERN_* settings from
+ // ILockSettings.getString(). For security reasons, we now just return a
+ // stubbed-out value. Note: the only one of these three settings actually known
+ // to have been used was LOCK_PATTERN_ENABLED, and ILockSettings.getString()
+ // already always returned "0" for that starting in Android 11.
+ return "0";
}
+ throw new SecurityException("Settings.Secure." + name + " is deprecated and no" +
+ " longer accessible. See API documentation for potential replacements.");
}
return sNameValueCache.getStringForUser(resolver, name, userHandle);
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index f648ad4..434b1c7 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -53,9 +53,7 @@
import java.lang.ref.WeakReference;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
@@ -83,15 +81,16 @@
* A mapping between {@link SubscriptionManager.OnSubscriptionsChangedListener} and
* its callback IOnSubscriptionsChangedListener.
*/
- private final Map<SubscriptionManager.OnSubscriptionsChangedListener,
- IOnSubscriptionsChangedListener> mSubscriptionChangedListenerMap = new HashMap<>();
+ private final ConcurrentHashMap<SubscriptionManager.OnSubscriptionsChangedListener,
+ IOnSubscriptionsChangedListener>
+ mSubscriptionChangedListenerMap = new ConcurrentHashMap<>();
/**
* A mapping between {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} and
* its callback IOnSubscriptionsChangedListener.
*/
- private final Map<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener,
- IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap
- = new HashMap<>();
+ private final ConcurrentHashMap<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener,
+ IOnSubscriptionsChangedListener>
+ mOpportunisticSubscriptionChangedListenerMap = new ConcurrentHashMap<>();
/**
* A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 8a9445d..28b98d6 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,14 +16,10 @@
package com.android.internal.os;
-import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
-
-import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
-import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.type.DefaultMimeMapFactory;
import android.net.TrafficStats;
@@ -40,7 +36,6 @@
import dalvik.system.RuntimeHooks;
import dalvik.system.VMRuntime;
-import dalvik.system.ZipPathValidator;
import libcore.content.type.MimeMap;
@@ -265,31 +260,10 @@
*/
TrafficStats.attachSocketTagger();
- /*
- * Initialize the zip path validator callback depending on the targetSdk.
- */
- initZipPathValidatorCallback();
-
initialized = true;
}
/**
- * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
- * entry names.
- * Otherwise: clear the callback to the default validation.
- *
- * @hide
- */
- @TestApi
- public static void initZipPathValidatorCallback() {
- if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
- ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
- } else {
- ZipPathValidator.clearCallback();
- }
- }
-
- /**
* Returns an HTTP user agent of the form
* "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
*/
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 939a0e4..9c6a534 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -266,7 +266,8 @@
}
static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
- jlong size, jint channelType, jint fd, jobject hardwareBufferObj) {
+ jint deviceId, jlong size, jint channelType, jint fd,
+ jobject hardwareBufferObj) {
const native_handle_t *nativeHandle = nullptr;
NATIVE_HANDLE_DECLARE_STORAGE(ashmemHandle, 1, 0);
@@ -287,7 +288,7 @@
}
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
- return mgr->createDirectChannel(size, channelType, nativeHandle);
+ return mgr->createDirectChannel(deviceId, size, channelType, nativeHandle);
}
static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
@@ -532,7 +533,7 @@
{"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
- {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+ {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
(void *)nativeCreateDirectChannel},
{"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
index f97099d..16ed3ef 100644
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -17,9 +17,15 @@
package android.companion.virtual.sensor;
import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+import static android.hardware.SensorDirectChannel.RATE_STOP;
+import static android.hardware.SensorDirectChannel.RATE_VERY_FAST;
+import static android.hardware.SensorDirectChannel.TYPE_HARDWARE_BUFFER;
+import static android.hardware.SensorDirectChannel.TYPE_MEMORY_FILE;
import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -40,6 +46,8 @@
final VirtualSensorConfig originalConfig =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
+ .setHighestDirectReportRateLevel(RATE_VERY_FAST)
+ .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
.build();
final Parcel parcel = Parcel.obtain();
originalConfig.writeToParcel(parcel, /* flags= */ 0);
@@ -49,6 +57,39 @@
assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+ assertThat(recreatedConfig.getHighestDirectReportRateLevel()).isEqualTo(RATE_VERY_FAST);
+ assertThat(recreatedConfig.getDirectChannelTypesSupported()).isEqualTo(TYPE_MEMORY_FILE);
+ // From hardware/libhardware/include/hardware/sensors-base.h:
+ // 0x400 is SENSOR_FLAG_DIRECT_CHANNEL_ASHMEM (i.e. TYPE_MEMORY_FILE)
+ // 0x800 is SENSOR_FLAG_DIRECT_CHANNEL_GRALLOC (i.e. TYPE_HARDWARE_BUFFER)
+ // 7 is SENSOR_FLAG_SHIFT_DIRECT_REPORT
+ assertThat(recreatedConfig.getFlags()).isEqualTo(0x400 | RATE_VERY_FAST << 7);
+ }
+
+ @Test
+ public void hardwareBufferDirectChannelTypeSupported_throwsException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setDirectChannelTypesSupported(TYPE_HARDWARE_BUFFER | TYPE_MEMORY_FILE));
+ }
+
+ @Test
+ public void directChannelTypeSupported_missingHighestReportRateLevel_throwsException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
+ .build());
+ }
+
+ @Test
+ public void directChannelTypeSupported_missingDirectChannelTypeSupported_throwsException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setHighestDirectReportRateLevel(RATE_VERY_FAST)
+ .build());
}
@Test
@@ -56,5 +97,8 @@
final VirtualSensorConfig config =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
assertThat(config.getVendor()).isNull();
+ assertThat(config.getHighestDirectReportRateLevel()).isEqualTo(RATE_STOP);
+ assertThat(config.getDirectChannelTypesSupported()).isEqualTo(0);
+ assertThat(config.getFlags()).isEqualTo(0);
}
}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index ef106bc..ee1b2aa 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -43,14 +43,56 @@
assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
}
- @SmallTest
- fun missingLookupTableReturnsNull() {
- assertThat(FontScaleConverterFactory.forScale(3F)).isNull()
+ @LargeTest
+ @Test
+ fun missingLookupTablePastEnd_returnsLinear() {
+ val table = FontScaleConverterFactory.forScale(3F)!!
+ generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
+ .map {
+ assertThat(table.convertSpToDp(it)).isWithin(CONVERSION_TOLERANCE).of(it * 3f)
+ }
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(24f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(150f)
+ assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(300f)
}
@SmallTest
- fun missingLookupTable105ReturnsNull() {
- assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull()
+ fun missingLookupTable110_returnsInterpolated() {
+ val table = FontScaleConverterFactory.forScale(1.1F)!!
+
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.1f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.1f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(11f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.1f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ assertThat(table.convertSpToDp(50F)).isLessThan(50f * 1.1f)
+ assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.1f)
+ }
+
+ @Test
+ fun missingLookupTable199_returnsInterpolated() {
+ val table = FontScaleConverterFactory.forScale(1.9999F)!!
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+ }
+
+ @Test
+ fun missingLookupTable160_returnsInterpolated() {
+ val table = FontScaleConverterFactory.forScale(1.6F)!!
+ assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f * 1.6F)
+ assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.6F)
+ assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f * 1.6F)
+ assertThat(table.convertSpToDp(20F)).isLessThan(20f * 1.6F)
+ assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.6F)
+ assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.6F)
+ assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
}
@SmallTest
@@ -83,7 +125,7 @@
@Test
fun allFeasibleScalesAndConversionsDoNotCrash() {
generateSequenceOfFractions(-10f..10f, step = 0.01f)
- .mapNotNull{ FontScaleConverterFactory.forScale(it) }
+ .mapNotNull{ FontScaleConverterFactory.forScale(it) }!!
.flatMap{ table ->
generateSequenceOfFractions(-2000f..2000f, step = 0.01f)
.map{ Pair(table, it) }
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 2e31bb5..b6fc137 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -32,16 +32,18 @@
import android.test.mock.MockContentResolver;
import android.util.MemoryIntArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -59,42 +61,63 @@
private static final String NAMESPACE = "namespace";
private static final String NAMESPACE2 = "namespace2";
+ private static final String SETTING = "test_setting";
+ private static final String SETTING2 = "test_setting2";
+
@Mock
private IContentProvider mMockIContentProvider;
@Mock
private ContentProvider mMockContentProvider;
private MockContentResolver mMockContentResolver;
- private MemoryIntArray mCacheGenerationStore;
- private int mCurrentGeneration = 123;
+ private MemoryIntArray mConfigsCacheGenerationStore;
+ private MemoryIntArray mSettingsCacheGenerationStore;
- private HashMap<String, HashMap<String, String>> mStorage;
+ private HashMap<String, HashMap<String, String>> mConfigsStorage;
+ private HashMap<String, String> mSettingsStorage;
+
@Before
public void setUp() throws Exception {
Settings.Config.clearProviderForTest();
+ Settings.Secure.clearProviderForTest();
MockitoAnnotations.initMocks(this);
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
- mMockContentResolver = new MockContentResolver(InstrumentationRegistry
- .getInstrumentation().getContext());
+ mMockContentResolver = new MockContentResolver(
+ InstrumentationRegistry.getInstrumentation().getContext());
mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
mMockContentProvider);
- mCacheGenerationStore = new MemoryIntArray(1);
- mStorage = new HashMap<>();
+ mMockContentResolver.addProvider(Settings.Secure.CONTENT_URI.getAuthority(),
+ mMockContentProvider);
+ mConfigsCacheGenerationStore = new MemoryIntArray(2);
+ mConfigsCacheGenerationStore.set(0, 123);
+ mConfigsCacheGenerationStore.set(1, 456);
+ mSettingsCacheGenerationStore = new MemoryIntArray(2);
+ mSettingsCacheGenerationStore.set(0, 234);
+ mSettingsCacheGenerationStore.set(1, 567);
+ mConfigsStorage = new HashMap<>();
+ mSettingsStorage = new HashMap<>();
// Stores keyValues for a given prefix and increments the generation. (Note that this
// increments the generation no matter what, it doesn't pay attention to if anything
// actually changed).
when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
HashMap<String, String> keyValues =
(HashMap<String, String>) incomingBundle.getSerializable(
- Settings.CALL_METHOD_FLAGS_KEY);
+ Settings.CALL_METHOD_FLAGS_KEY, HashMap.class);
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
- mStorage.put(prefix, keyValues);
- mCacheGenerationStore.set(0, ++mCurrentGeneration);
-
+ mConfigsStorage.put(prefix, keyValues);
+ int currentGeneration;
+ // Different prefixes have different generation codes
+ if (prefix.equals(NAMESPACE + "/")) {
+ currentGeneration = mConfigsCacheGenerationStore.get(0);
+ mConfigsCacheGenerationStore.set(0, ++currentGeneration);
+ } else if (prefix.equals(NAMESPACE2 + "/")) {
+ currentGeneration = mConfigsCacheGenerationStore.get(1);
+ mConfigsCacheGenerationStore.set(1, ++currentGeneration);
+ }
Bundle result = new Bundle();
result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
Settings.SET_ALL_RESULT_SUCCESS);
@@ -102,32 +125,87 @@
});
// Returns the keyValues corresponding to a namespace, or an empty map if the namespace
- // doesn't have anything stored for it. Returns the generation key if the caller asked
- // for one.
+ // doesn't have anything stored for it. Returns the generation key if the map isn't empty
+ // and the caller asked for the generation key.
when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
- if (!mStorage.containsKey(prefix)) {
- mStorage.put(prefix, new HashMap<>());
+ if (!mConfigsStorage.containsKey(prefix)) {
+ mConfigsStorage.put(prefix, new HashMap<>());
}
- HashMap<String, String> keyValues = mStorage.get(prefix);
+ HashMap<String, String> keyValues = mConfigsStorage.get(prefix);
Bundle bundle = new Bundle();
bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
- if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+ if (!keyValues.isEmpty() && incomingBundle.containsKey(
+ Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+ int index = prefix.equals(NAMESPACE + "/") ? 0 : 1;
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
- mCacheGenerationStore);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
+ mConfigsCacheGenerationStore);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
- mCacheGenerationStore.get(0));
+ mConfigsCacheGenerationStore.get(index));
}
return bundle;
});
+
+ // Stores value for a given setting's name and increments the generation. (Note that this
+ // increments the generation no matter what, it doesn't pay attention to if anything
+ // actually changed).
+ when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
+ Bundle incomingBundle = invocationOnMock.getArgument(4);
+ String key = invocationOnMock.getArgument(3);
+ String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
+ mSettingsStorage.put(key, value);
+ int currentGeneration;
+ // Different settings have different generation codes
+ if (key.equals(SETTING)) {
+ currentGeneration = mSettingsCacheGenerationStore.get(0);
+ mSettingsCacheGenerationStore.set(0, ++currentGeneration);
+ } else if (key.equals(SETTING2)) {
+ currentGeneration = mSettingsCacheGenerationStore.get(1);
+ mSettingsCacheGenerationStore.set(1, ++currentGeneration);
+ }
+ return null;
+ });
+
+ // Returns the value corresponding to a setting, or null if the setting
+ // doesn't have a value stored for it. Returns the generation key if the value isn't null
+ // and the caller asked for the generation key.
+ when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
+ Bundle incomingBundle = invocationOnMock.getArgument(4);
+ String key = invocationOnMock.getArgument(3);
+ String value = mSettingsStorage.get(key);
+
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(Settings.NameValueTable.VALUE, value);
+
+ if (value != null && incomingBundle.containsKey(
+ Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+ int index = key.equals(SETTING) ? 0 : 1;
+ bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+ mSettingsCacheGenerationStore);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+ mSettingsCacheGenerationStore.get(index));
+ }
+ return bundle;
+ });
+ }
+
+ @After
+ public void cleanUp() throws IOException {
+ mConfigsStorage.clear();
+ mSettingsStorage.clear();
}
@Test
@@ -135,16 +213,13 @@
HashMap<String, String> keyValues = new HashMap<>();
keyValues.put("a", "b");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class));
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE,
- Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ NAMESPACE, Collections.emptyList());
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
@@ -156,14 +231,12 @@
keyValues.put("a", "c");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class));
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
@@ -177,36 +250,31 @@
HashMap<String, String> keyValues = new HashMap<>();
keyValues.put("a", "b");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+ verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class));
+ Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
+ NAMESPACE, Collections.emptyList());
+ verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_LIST_CONFIG),
+ any(), any(Bundle.class));
+ assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+
HashMap<String, String> keyValues2 = new HashMap<>();
keyValues2.put("c", "d");
keyValues2.put("e", "f");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class));
-
- Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE,
- Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
- assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE2,
- Collections.emptyList());
+ NAMESPACE2, Collections.emptyList());
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
+ // Modifying the second namespace doesn't affect the cache of the first namespace
verifyNoMoreInteractions(mMockIContentProvider);
assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
@@ -219,17 +287,90 @@
@Test
public void testCaching_emptyNamespace() throws Exception {
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE,
- Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ NAMESPACE, Collections.emptyList());
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues).isEmpty();
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
- verifyNoMoreInteractions(mMockIContentProvider);
+ // Empty list won't be cached
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(cachedKeyValues).isEmpty();
}
+ @Test
+ public void testCaching_singleSetting() throws Exception {
+ Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isEqualTo("a");
+
+ String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue).isEqualTo("a");
+
+ // Modify the value to invalidate the cache.
+ Settings.Secure.putString(mMockContentResolver, SETTING, "b");
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue2).isEqualTo("b");
+
+ String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue2).isEqualTo("b");
+ }
+
+ @Test
+ public void testCaching_multipleSettings() throws Exception {
+ Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isEqualTo("a");
+
+ Settings.Secure.putString(mMockContentResolver, SETTING2, "b");
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue2).isEqualTo("b");
+
+ String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ // Modifying the second setting doesn't affect the cache of the first setting
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue).isEqualTo("a");
+
+ String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue2).isEqualTo("b");
+ }
+
+ @Test
+ public void testCaching_nullSetting() throws Exception {
+ String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isNull();
+
+ String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ // Empty list won't be cached
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(cachedValue).isNull();
+ }
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ca1367a..0692052 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -248,17 +248,22 @@
@Test
public void testSystemDrivenInsetsAnimationLoggingListener_onReady() {
+ var loggingListener = mock(WindowInsetsAnimationControlListener.class);
+
prepareControls();
// only the original thread that created view hierarchy can touch its views
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- WindowInsetsAnimationControlListener loggingListener =
- mock(WindowInsetsAnimationControlListener.class);
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
- verify(loggingListener).onReady(notNull(), anyInt());
+ // When using the animation thread, this must not invoke onReady()
+ mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
});
+ // Wait for onReady() being dispatched on the animation thread.
+ InsetsAnimationThread.get().getThreadHandler().runWithScissors(() -> {}, 500);
+
+ verify(loggingListener).onReady(notNull(), anyInt());
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
deleted file mode 100644
index c540a15..0000000
--- a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertThrows;
-
-import android.compat.testing.PlatformCompatChangeRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Test SafeZipPathCallback.
- */
-@RunWith(AndroidJUnit4.class)
-public class SafeZipPathValidatorCallbackTest {
- @Rule
- public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
-
- @Before
- public void setUp() {
- RuntimeInit.initZipPathValidatorCallback();
- }
-
- @Test
- @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
- public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
- throws Exception {
- final String[] dangerousEntryNames = {
- "../foo.bar",
- "foo/../bar.baz",
- "foo/../../bar.baz",
- "foo.bar/..",
- "foo.bar/../",
- "..",
- "../",
- "/foo",
- };
- for (String entryName : dangerousEntryNames) {
- final File tempFile = File.createTempFile("smdc", "zip");
- try {
- writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
-
- assertThrows(
- "ZipException expected for entry: " + entryName,
- ZipException.class,
- () -> {
- new ZipFile(tempFile);
- });
- } finally {
- tempFile.delete();
- }
- }
- }
-
- @Test
- @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
- public void
- testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
- throws Exception {
- final String[] dangerousEntryNames = {
- "../foo.bar",
- "foo/../bar.baz",
- "foo/../../bar.baz",
- "foo.bar/..",
- "foo.bar/../",
- "..",
- "../",
- "/foo",
- };
- for (String entryName : dangerousEntryNames) {
- byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
- try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
- assertThrows(
- "ZipException expected for entry: " + entryName,
- ZipException.class,
- () -> {
- zis.getNextEntry();
- });
- }
- }
- }
-
- @Test
- @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
- public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
- throws Exception {
- final String[] normalEntryNames = {
- "foo", "foo.bar", "foo..bar",
- };
- for (String entryName : normalEntryNames) {
- final File tempFile = File.createTempFile("smdc", "zip");
- try {
- writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
- try {
- new ZipFile((tempFile));
- } catch (ZipException e) {
- throw new AssertionError("ZipException not expected for entry: " + entryName);
- }
- } finally {
- tempFile.delete();
- }
- }
- }
-
- @Test
- @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
- public void
- testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
- throws Exception {
- final String[] normalEntryNames = {
- "foo", "foo.bar", "foo..bar",
- };
- for (String entryName : normalEntryNames) {
- byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
- try {
- ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
- zis.getNextEntry();
- } catch (ZipException e) {
- throw new AssertionError("ZipException not expected for entry: " + entryName);
- }
- }
- }
-
- @Test
- @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
- public void
- testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
- throws Exception {
- final String[] entryNames = {
- "../foo.bar",
- "foo/../bar.baz",
- "foo/../../bar.baz",
- "foo.bar/..",
- "foo.bar/../",
- "..",
- "../",
- "/foo",
- "foo",
- "foo.bar",
- "foo..bar",
- };
- for (String entryName : entryNames) {
- final File tempFile = File.createTempFile("smdc", "zip");
- try {
- writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
- try {
- new ZipFile((tempFile));
- } catch (ZipException e) {
- throw new AssertionError("ZipException not expected for entry: " + entryName);
- }
- } finally {
- tempFile.delete();
- }
- }
- }
-
- @Test
- @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
- public void
- testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
- throws Exception {
- final String[] entryNames = {
- "../foo.bar",
- "foo/../bar.baz",
- "foo/../../bar.baz",
- "foo.bar/..",
- "foo.bar/../",
- "..",
- "../",
- "/foo",
- "foo",
- "foo.bar",
- "foo..bar",
- };
- for (String entryName : entryNames) {
- byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
- try {
- ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
- zis.getNextEntry();
- } catch (ZipException e) {
- throw new AssertionError("ZipException not expected for entry: " + entryName);
- }
- }
- }
-
- private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
- throws IOException {
- FileOutputStream tempFileStream = new FileOutputStream(tempFile);
- writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
- tempFileStream.close();
- }
-
- private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
- throws IOException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- writeZipOutputStreamWithEmptyEntry(bos, entryName);
- return bos.toByteArray();
- }
-
- private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
- throws IOException {
- ZipOutputStream zos = new ZipOutputStream(os);
- ZipEntry entry = new ZipEntry(entryName);
- zos.putNextEntry(entry);
- zos.write(new byte[2]);
- zos.closeEntry();
- zos.close();
- }
-}
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 6a1313e..66fabec 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -341,7 +341,6 @@
* @hide so only calls from module can utilize it
*/
long getNativeWrapperInstance() {
- nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed);
return mNativeMeshWrapper;
}
@@ -383,5 +382,4 @@
private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
- private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed);
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index bcbe706..536bb49 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -332,6 +332,7 @@
"jni/android_graphics_Matrix.cpp",
"jni/android_graphics_Picture.cpp",
"jni/android_graphics_DisplayListCanvas.cpp",
+ "jni/android_graphics_Mesh.cpp",
"jni/android_graphics_RenderNode.cpp",
"jni/android_nio_utils.cpp",
"jni/android_util_PathParser.cpp",
@@ -351,7 +352,6 @@
"jni/ImageDecoder.cpp",
"jni/Interpolator.cpp",
"jni/MeshSpecification.cpp",
- "jni/Mesh.cpp",
"jni/MaskFilter.cpp",
"jni/NinePatch.cpp",
"jni/NinePatchPeeker.cpp",
@@ -538,6 +538,7 @@
"Interpolator.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
+ "Mesh.cpp",
"MemoryPolicy.cpp",
"PathParser.cpp",
"Properties.cpp",
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index e2127ef..a18ba1c 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,4 +52,5 @@
X(DrawVectorDrawable)
X(DrawRippleDrawable)
X(DrawWebView)
-X(DrawMesh)
+X(DrawSkMesh)
+X(DrawMesh)
\ No newline at end of file
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 2f0f7f5..41ced8c 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,8 +53,8 @@
// Whether or not to only purge scratch resources when triggering UI Hidden or background
// collection
bool purgeScratchOnly = true;
- // Whether or not to trigger releasing GPU context when all contexts are stopped
- bool releaseContextOnStoppedOnly = true;
+ // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+ bool releaseContextOnStoppedOnly = false;
};
const MemoryPolicy& loadMemoryPolicy();
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
new file mode 100644
index 0000000..e59bc95
--- /dev/null
+++ b/libs/hwui/Mesh.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Mesh.h"
+
+#include <GLES/gl.h>
+#include <SkMesh.h>
+
+#include "SafeMath.h"
+
+static size_t min_vcount_for_mode(SkMesh::Mode mode) {
+ switch (mode) {
+ case SkMesh::Mode::kTriangles:
+ return 3;
+ case SkMesh::Mode::kTriangleStrip:
+ return 3;
+ }
+}
+
+// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
+std::tuple<bool, SkString> Mesh::validate() {
+#define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__))
+ if (!mMeshSpec) {
+ FAIL_MESH_VALIDATE("MeshSpecification is required.");
+ }
+ if (mVertexBufferData.empty()) {
+ FAIL_MESH_VALIDATE("VertexBuffer is required.");
+ }
+
+ auto meshStride = mMeshSpec->stride();
+ auto meshMode = SkMesh::Mode(mMode);
+ SafeMath sm;
+ size_t vsize = sm.mul(meshStride, mVertexCount);
+ if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+ FAIL_MESH_VALIDATE(
+ "The vertex buffer offset and vertex count reads beyond the end of the"
+ " vertex buffer.");
+ }
+
+ if (mVertexOffset % meshStride != 0) {
+ FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
+ mVertexOffset, meshStride);
+ }
+
+ if (size_t uniformSize = mMeshSpec->uniformSize()) {
+ if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+ FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
+ mBuilder->fUniforms->size(), uniformSize);
+ }
+ }
+
+ auto modeToStr = [](SkMesh::Mode m) {
+ switch (m) {
+ case SkMesh::Mode::kTriangles:
+ return "triangles";
+ case SkMesh::Mode::kTriangleStrip:
+ return "triangle-strip";
+ }
+ };
+ if (!mIndexBufferData.empty()) {
+ if (mIndexCount < min_vcount_for_mode(meshMode)) {
+ FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
+ modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+ }
+ size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
+ if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+ FAIL_MESH_VALIDATE(
+ "The index buffer offset and index count reads beyond the end of the"
+ " index buffer.");
+ }
+ // If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
+ if (!SkIsAlign2(mIndexOffset)) {
+ FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
+ }
+ } else {
+ if (mVertexCount < min_vcount_for_mode(meshMode)) {
+ FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
+ modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+ }
+ SkASSERT(!fICount);
+ SkASSERT(!fIOffset);
+ }
+
+ if (!sm.ok()) {
+ FAIL_MESH_VALIDATE("Overflow");
+ }
+#undef FAIL_MESH_VALIDATE
+ return {true, {}};
+}
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
new file mode 100644
index 0000000..9836817
--- /dev/null
+++ b/libs/hwui/Mesh.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MESH_H_
+#define MESH_H_
+
+#include <GrDirectContext.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+class MeshUniformBuilder {
+public:
+ struct MeshUniform {
+ template <typename T>
+ std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+ const T& val) {
+ if (!fVar) {
+ LOG_FATAL("Assigning to missing variable");
+ } else if (sizeof(val) != fVar->sizeInBytes()) {
+ LOG_FATAL("Incorrect value size");
+ } else {
+ void* dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ memcpy(dst, &val, sizeof(val));
+ }
+ }
+
+ MeshUniform& operator=(const SkMatrix& val) {
+ if (!fVar) {
+ LOG_FATAL("Assigning to missing variable");
+ } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+ LOG_FATAL("Incorrect value size");
+ } else {
+ float* data = reinterpret_cast<float*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ data[0] = val.get(0);
+ data[1] = val.get(3);
+ data[2] = val.get(6);
+ data[3] = val.get(1);
+ data[4] = val.get(4);
+ data[5] = val.get(7);
+ data[6] = val.get(2);
+ data[7] = val.get(5);
+ data[8] = val.get(8);
+ }
+ return *this;
+ }
+
+ template <typename T>
+ bool set(const T val[], const int count) {
+ static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+ if (!fVar) {
+ LOG_FATAL("Assigning to missing variable");
+ return false;
+ } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+ LOG_FATAL("Incorrect value size");
+ return false;
+ } else {
+ void* dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ memcpy(dst, val, sizeof(T) * count);
+ }
+ return true;
+ }
+
+ MeshUniformBuilder* fOwner;
+ const SkRuntimeEffect::Uniform* fVar;
+ };
+ MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+ explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+ fMeshSpec = sk_sp(meshSpec);
+ fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
+ }
+
+ sk_sp<SkData> fUniforms;
+
+private:
+ void* writableUniformData() {
+ if (!fUniforms->unique()) {
+ fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+ }
+ return fUniforms->writable_data();
+ }
+
+ sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+class Mesh {
+public:
+ Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+ size_t vertexBufferSize, jint vertexCount, jint vertexOffset,
+ std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+ : mMeshSpec(meshSpec)
+ , mMode(mode)
+ , mVertexCount(vertexCount)
+ , mVertexOffset(vertexOffset)
+ , mBuilder(std::move(builder))
+ , mBounds(bounds) {
+ copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+ }
+
+ Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+ size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer,
+ size_t indexBufferSize, jint indexCount, jint indexOffset,
+ std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+ : mMeshSpec(meshSpec)
+ , mMode(mode)
+ , mVertexCount(vertexCount)
+ , mVertexOffset(vertexOffset)
+ , mIndexCount(indexCount)
+ , mIndexOffset(indexOffset)
+ , mBuilder(std::move(builder))
+ , mBounds(bounds) {
+ copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+ copyToVector(mIndexBufferData, indexBuffer, indexBufferSize);
+ }
+
+ Mesh(Mesh&&) = default;
+
+ Mesh& operator=(Mesh&&) = default;
+
+ [[nodiscard]] std::tuple<bool, SkString> validate();
+
+ void updateSkMesh(GrDirectContext* context) const {
+ GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
+ if (context) {
+ genId = context->directContextID();
+ }
+
+ if (mIsDirty || genId != mGenerationId) {
+ auto vb = SkMesh::MakeVertexBuffer(
+ context, reinterpret_cast<const void*>(mVertexBufferData.data()),
+ mVertexBufferData.size());
+ auto meshMode = SkMesh::Mode(mMode);
+ if (!mIndexBufferData.empty()) {
+ auto ib = SkMesh::MakeIndexBuffer(
+ context, reinterpret_cast<const void*>(mIndexBufferData.data()),
+ mIndexBufferData.size());
+ mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+ ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
+ mBounds)
+ .mesh;
+ } else {
+ mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+ mBuilder->fUniforms, mBounds)
+ .mesh;
+ }
+ mIsDirty = false;
+ mGenerationId = genId;
+ }
+ }
+
+ SkMesh& getSkMesh() const {
+ LOG_FATAL_IF(mIsDirty,
+ "Attempt to obtain SkMesh when Mesh is dirty, did you "
+ "forget to call updateSkMesh with a GrDirectContext? "
+ "Defensively creating a CPU mesh");
+ return mMesh;
+ }
+
+ void markDirty() { mIsDirty = true; }
+
+ MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+
+private:
+ void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
+ if (src) {
+ dst.resize(srcSize);
+ memcpy(dst.data(), src, srcSize);
+ }
+ }
+
+ sk_sp<SkMeshSpecification> mMeshSpec;
+ int mMode = 0;
+
+ std::vector<uint8_t> mVertexBufferData;
+ size_t mVertexCount = 0;
+ size_t mVertexOffset = 0;
+
+ std::vector<uint8_t> mIndexBufferData;
+ size_t mIndexCount = 0;
+ size_t mIndexOffset = 0;
+
+ std::unique_ptr<MeshUniformBuilder> mBuilder;
+ SkRect mBounds{};
+
+ mutable SkMesh mMesh{};
+ mutable bool mIsDirty = true;
+ mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+};
+#endif // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 659aec0..0b58406 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -24,6 +24,7 @@
#include <experimental/type_traits>
#include <utility>
+#include "Mesh.h"
#include "SkAndroidFrameworkUtils.h"
#include "SkBlendMode.h"
#include "SkCanvas.h"
@@ -502,14 +503,14 @@
c->drawVertices(vertices, mode, paint);
}
};
-struct DrawMesh final : Op {
- static const auto kType = Type::DrawMesh;
- DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+struct DrawSkMesh final : Op {
+ static const auto kType = Type::DrawSkMesh;
+ DrawSkMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
: cpuMesh(mesh), blender(std::move(blender)), paint(paint) {
isGpuBased = false;
}
- SkMesh cpuMesh;
+ const SkMesh& cpuMesh;
mutable SkMesh gpuMesh;
sk_sp<SkBlender> blender;
SkPaint paint;
@@ -517,6 +518,7 @@
mutable GrDirectContext::DirectContextID contextId;
void draw(SkCanvas* c, const SkMatrix&) const {
GrDirectContext* directContext = c->recordingContext()->asDirectContext();
+
GrDirectContext::DirectContextID id = directContext->directContextID();
if (!isGpuBased || contextId != id) {
sk_sp<SkMesh::VertexBuffer> vb =
@@ -543,6 +545,18 @@
c->drawMesh(gpuMesh, blender, paint);
}
};
+
+struct DrawMesh final : Op {
+ static const auto kType = Type::DrawMesh;
+ DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+ : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+ const Mesh& mesh;
+ sk_sp<SkBlender> blender;
+ SkPaint paint;
+
+ void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh.getSkMesh(), blender, paint); }
+};
struct DrawAtlas final : Op {
static const auto kType = Type::DrawAtlas;
DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -859,6 +873,10 @@
}
void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
const SkPaint& paint) {
+ this->push<DrawSkMesh>(0, mesh, blender, paint);
+}
+void DisplayListData::drawMesh(const Mesh& mesh, const sk_sp<SkBlender>& blender,
+ const SkPaint& paint) {
this->push<DrawMesh>(0, mesh, blender, paint);
}
void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
@@ -1205,6 +1223,9 @@
const SkPaint& paint) {
fDL->drawMesh(mesh, blender, paint);
}
+void RecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+ fDL->drawMesh(mesh, blender, paint);
+}
void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
const SkRect texs[], const SkColor colors[], int count,
SkBlendMode bmode, const SkSamplingOptions& sampling,
@@ -1223,5 +1244,14 @@
fDL->drawWebView(drawable);
}
+[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
+ LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
+ if (meshWrapper) {
+ return meshWrapper->getSkMesh();
+ } else {
+ return *mesh;
+ }
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 8409e13..1f4ba5d 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -28,6 +28,7 @@
#include <log/log.h>
#include <cstdlib>
+#include <utility>
#include <vector>
#include "CanvasTransform.h"
@@ -40,6 +41,7 @@
enum class SkBlendMode;
class SkRRect;
+class Mesh;
namespace android {
namespace uirenderer {
@@ -66,6 +68,18 @@
static_assert(sizeof(DisplayListOp) == 4);
+class DrawMeshPayload {
+public:
+ explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
+ explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
+
+ [[nodiscard]] const SkMesh& getSkMesh() const;
+
+private:
+ const SkMesh* mesh = nullptr;
+ const Mesh* meshWrapper = nullptr;
+};
+
struct DrawImagePayload {
explicit DrawImagePayload(Bitmap& bitmap)
: image(bitmap.makeImage()), palette(bitmap.palette()) {
@@ -143,6 +157,7 @@
void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+ void drawMesh(const Mesh&, const sk_sp<SkBlender>&, const SkPaint&);
void drawAnnotation(const SkRect&, const char*, SkData*);
void drawDrawable(SkDrawable*, const SkMatrix*);
@@ -247,6 +262,7 @@
SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
+ void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint);
void drawVectorDrawable(VectorDrawableRoot* tree);
void drawWebView(skiapipeline::FunctorDrawable*);
diff --git a/libs/hwui/SafeMath.h b/libs/hwui/SafeMath.h
new file mode 100644
index 0000000..4d6adf5
--- /dev/null
+++ b/libs/hwui/SafeMath.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SkSafeMath_DEFINED
+#define SkSafeMath_DEFINED
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+// Copy of Skia's SafeMath API used to validate Mesh parameters to support
+// deferred creation of SkMesh instances on RenderThread.
+// SafeMath always check that a series of operations do not overflow.
+// This must be correct for all platforms, because this is a check for safety at runtime.
+
+class SafeMath {
+public:
+ SafeMath() = default;
+
+ bool ok() const { return fOK; }
+ explicit operator bool() const { return fOK; }
+
+ size_t mul(size_t x, size_t y) {
+ return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y);
+ }
+
+ size_t add(size_t x, size_t y) {
+ size_t result = x + y;
+ fOK &= result >= x;
+ return result;
+ }
+
+ /**
+ * Return a + b, unless this result is an overflow/underflow. In those cases, fOK will
+ * be set to false, and it is undefined what this returns.
+ */
+ int addInt(int a, int b) {
+ if (b < 0 && a < std::numeric_limits<int>::min() - b) {
+ fOK = false;
+ return a;
+ } else if (b > 0 && a > std::numeric_limits<int>::max() - b) {
+ fOK = false;
+ return a;
+ }
+ return a + b;
+ }
+
+ // These saturate to their results
+ static size_t Add(size_t x, size_t y) {
+ SafeMath tmp;
+ size_t sum = tmp.add(x, y);
+ return tmp.ok() ? sum : SIZE_MAX;
+ }
+
+ static size_t Mul(size_t x, size_t y) {
+ SafeMath tmp;
+ size_t prod = tmp.mul(x, y);
+ return tmp.ok() ? prod : SIZE_MAX;
+ }
+
+private:
+ uint32_t mul32(uint32_t x, uint32_t y) {
+ uint64_t bx = x;
+ uint64_t by = y;
+ uint64_t result = bx * by;
+ fOK &= result >> 32 == 0;
+ // Overflow information is capture in fOK. Return the result modulo 2^32.
+ return (uint32_t)result;
+ }
+
+ uint64_t mul64(uint64_t x, uint64_t y) {
+ if (x <= std::numeric_limits<uint64_t>::max() >> 32 &&
+ y <= std::numeric_limits<uint64_t>::max() >> 32) {
+ return x * y;
+ } else {
+ auto hi = [](uint64_t x) { return x >> 32; };
+ auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
+
+ uint64_t lx_ly = lo(x) * lo(y);
+ uint64_t hx_ly = hi(x) * lo(y);
+ uint64_t lx_hy = lo(x) * hi(y);
+ uint64_t hx_hy = hi(x) * hi(y);
+ uint64_t result = 0;
+ result = this->add(lx_ly, (hx_ly << 32));
+ result = this->add(result, (lx_hy << 32));
+ fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0;
+
+ return result;
+ }
+ }
+ bool fOK = true;
+};
+
+#endif // SkSafeMath_DEFINED
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d0124f5..7a12769 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -39,21 +39,22 @@
#include <SkShader.h>
#include <SkTextBlob.h>
#include <SkVertices.h>
+#include <log/log.h>
+#include <ui/FatVector.h>
#include <memory>
#include <optional>
#include <utility>
#include "CanvasProperty.h"
+#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
#include "hwui/PaintFilter.h"
-#include <log/log.h>
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/HolePunch.h"
-#include <ui/FatVector.h>
namespace android {
@@ -572,8 +573,14 @@
applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
}
-void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
- mCanvas->drawMesh(mesh, blender, paint);
+void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+ GrDirectContext* context = nullptr;
+ auto recordingContext = mCanvas->recordingContext();
+ if (recordingContext) {
+ context = recordingContext->asDirectContext();
+ }
+ mesh.updateSkMesh(context);
+ mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index f2c286a..b785989 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -129,8 +129,7 @@
float sweepAngle, bool useCenter, const Paint& paint) override;
virtual void drawPath(const SkPath& path, const Paint& paint) override;
virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
- virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
- const SkPaint& paint) override;
+ virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 2a20191..44ee31d 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -16,18 +16,17 @@
#pragma once
-#include <cutils/compiler.h>
-#include <utils/Functor.h>
#include <SaveFlags.h>
-
-#include <androidfw/ResourceTypes.h>
-#include "Properties.h"
-#include "pipeline/skia/AnimatedDrawables.h"
-#include "utils/Macros.h"
-
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkMatrix.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <utils/Functor.h>
+
+#include "Properties.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include "utils/Macros.h"
class SkAnimatedImage;
enum class SkBlendMode;
@@ -35,6 +34,7 @@
class SkRRect;
class SkRuntimeShaderBuilder;
class SkVertices;
+class Mesh;
namespace minikin {
class Font;
@@ -227,7 +227,7 @@
float sweepAngle, bool useCenter, const Paint& paint) = 0;
virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
- virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0;
+ virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender>, const Paint& paint) = 0;
// Bitmap-based
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/jni/Interpolator.cpp b/libs/hwui/jni/Interpolator.cpp
index fc3d70b..c71e308 100644
--- a/libs/hwui/jni/Interpolator.cpp
+++ b/libs/hwui/jni/Interpolator.cpp
@@ -24,12 +24,8 @@
AutoJavaFloatArray autoValues(env, valueArray);
AutoJavaFloatArray autoBlend(env, blendArray, 4);
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* scalars = autoValues.ptr();
SkScalar* blend = autoBlend.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
interp->setKeyFrame(index, msec, scalars, blend);
}
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
deleted file mode 100644
index b13d9ba..0000000
--- a/libs/hwui/jni/Mesh.cpp
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <GLES/gl.h>
-#include <Mesh.h>
-#include <SkMesh.h>
-
-#include "GraphicsJNI.h"
-#include "graphics_jni_helpers.h"
-
-namespace android {
-
-sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
- jboolean isDirect) {
- auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
- auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
- return vertexBuffer;
-}
-
-sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
- jboolean isDirect) {
- auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
- auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
- return indexBuffer;
-}
-
-static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
- jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
- jfloat right, jfloat bottom) {
- auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
- sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
- genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
- auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshResult = SkMesh::Make(
- skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
- SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
- if (!meshResult.error.isEmpty()) {
- jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
- }
-
- auto meshPtr = std::make_unique<MeshWrapper>(
- MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
- return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
- jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
- jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
- jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
- auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
- sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
- genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect);
- sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
- genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
- auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-
- auto meshResult = SkMesh::MakeIndexed(
- skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
- skIndexBuffer, indexCount, indexOffset,
- SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
- if (!meshResult.error.isEmpty()) {
- jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
- }
- auto meshPtr = std::make_unique<MeshWrapper>(
- MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
- return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
- auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
- auto mesh = wrapper->mesh;
- if (indexed) {
- wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(),
- sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
- mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
- mesh.indexCount(), mesh.indexOffset(),
- wrapper->builder.fUniforms, mesh.bounds())
- .mesh;
- } else {
- wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(),
- sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
- mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds())
- .mesh;
- }
-}
-
-static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
- va_end(args);
- return ret;
-}
-
-static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
- switch (type) {
- case SkRuntimeEffect::Uniform::Type::kFloat:
- case SkRuntimeEffect::Uniform::Type::kFloat2:
- case SkRuntimeEffect::Uniform::Type::kFloat3:
- case SkRuntimeEffect::Uniform::Type::kFloat4:
- case SkRuntimeEffect::Uniform::Type::kFloat2x2:
- case SkRuntimeEffect::Uniform::Type::kFloat3x3:
- case SkRuntimeEffect::Uniform::Type::kFloat4x4:
- return false;
- case SkRuntimeEffect::Uniform::Type::kInt:
- case SkRuntimeEffect::Uniform::Type::kInt2:
- case SkRuntimeEffect::Uniform::Type::kInt3:
- case SkRuntimeEffect::Uniform::Type::kInt4:
- return true;
- }
-}
-
-static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
- const char* uniformName, const float values[], int count,
- bool isColor) {
- MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
- if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
- } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
- if (isColor) {
- jniThrowExceptionFmt(
- env, "java/lang/IllegalArgumentException",
- "attempting to set a color uniform using the non-color specific APIs: %s %x",
- uniformName, uniform.fVar->flags);
- } else {
- ThrowIAEFmt(env,
- "attempting to set a non-color uniform using the setColorUniform APIs: %s",
- uniformName);
- }
- } else if (isIntUniformType(uniform.fVar->type)) {
- ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
- uniformName);
- } else if (!uniform.set<float>(values, count)) {
- ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * count);
- }
-}
-
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
- jfloat value1, jfloat value2, jfloat value3, jfloat value4,
- jint count) {
- auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
- ScopedUtfChars name(env, uniformName);
- const float values[4] = {value1, value2, value3, value4};
- nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
-}
-
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
- jfloatArray jvalues, jboolean isColor) {
- auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
- ScopedUtfChars name(env, jUniformName);
- AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
- nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
- autoValues.length(), isColor);
-}
-
-static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
- const char* uniformName, const int values[], int count) {
- MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
- if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
- } else if (!isIntUniformType(uniform.fVar->type)) {
- ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
- uniformName);
- } else if (!uniform.set<int>(values, count)) {
- ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * count);
- }
-}
-
-static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
- jint value1, jint value2, jint value3, jint value4, jint count) {
- auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
- ScopedUtfChars name(env, uniformName);
- const int values[4] = {value1, value2, value3, value4};
- nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
-}
-
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
- jintArray values) {
- auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
- ScopedUtfChars name(env, uniformName);
- AutoJavaIntArray autoValues(env, values, 0);
- nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
- autoValues.length());
-}
-
-static void MeshWrapper_destroy(MeshWrapper* wrapper) {
- delete wrapper;
-}
-
-static jlong getMeshFinalizer(JNIEnv*, jobject) {
- return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
-}
-
-static const JNINativeMethod gMeshMethods[] = {
- {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
- {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
- {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
- (void*)makeIndexed},
- {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
- {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
- {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
- {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
- {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
-
-int register_android_graphics_Mesh(JNIEnv* env) {
- android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
- return 0;
-}
-
-} // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
deleted file mode 100644
index 61c2260..0000000
--- a/libs/hwui/jni/Mesh.h
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-
-#include <SkMesh.h>
-#include <jni.h>
-
-#include <log/log.h>
-#include <utility>
-
-#include "graphics_jni_helpers.h"
-
-#define gIndexByteSize 2
-
-// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
-// direct and indrect buffers, allowing access to the underlying data in both
-// situations. If passed a null buffer, we will throw NullPointerException,
-// and c_data will return nullptr.
-//
-// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
-// conversion.
-class ScopedJavaNioBuffer {
-public:
- ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
- : mEnv(env), mBuffer(buffer) {
- if (buffer == nullptr) {
- mDataBase = nullptr;
- mData = nullptr;
- jniThrowNullPointerException(env);
- } else {
- mArray = (jarray) nullptr;
- if (isDirect) {
- mData = getDirectBufferPointer(mEnv, mBuffer);
- } else {
- mData = setIndirectData(size);
- }
- }
- }
-
- ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
-
- ~ScopedJavaNioBuffer() { reset(); }
-
- void reset() {
- if (mDataBase) {
- releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
- mDataBase = nullptr;
- }
- }
-
- ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
- if (this != &rhs) {
- reset();
-
- mEnv = rhs.mEnv;
- mBuffer = rhs.mBuffer;
- mDataBase = rhs.mDataBase;
- mData = rhs.mData;
- mArray = rhs.mArray;
- rhs.mEnv = nullptr;
- rhs.mData = nullptr;
- rhs.mBuffer = nullptr;
- rhs.mArray = nullptr;
- rhs.mDataBase = nullptr;
- }
- return *this;
- }
-
- const void* data() const { return mData; }
-
-private:
- /**
- * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
- * from a java.nio.Buffer.
- */
- void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
- if (buffer == nullptr) {
- return nullptr;
- }
-
- jint position;
- jint limit;
- jint elementSizeShift;
- jlong pointer;
- pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
- if (pointer == 0) {
- jniThrowException(mEnv, "java/lang/IllegalArgumentException",
- "Must use a native order direct Buffer");
- return nullptr;
- }
- pointer += position << elementSizeShift;
- return reinterpret_cast<void*>(pointer);
- }
-
- static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
- env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
- }
-
- static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
- jint* offset) {
- jint position;
- jint limit;
- jint elementSizeShift;
-
- jlong pointer;
- pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
- *remaining = (limit - position) << elementSizeShift;
- if (pointer != 0L) {
- *array = nullptr;
- pointer += position << elementSizeShift;
- return reinterpret_cast<void*>(pointer);
- }
-
- *array = jniGetNioBufferBaseArray(env, buffer);
- *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
- return nullptr;
- }
-
- /**
- * This is a copy of
- * static void android_glBufferData__IILjava_nio_Buffer_2I
- * from com_google_android_gles_jni_GLImpl.cpp
- */
- void* setIndirectData(jint size) {
- jint exception;
- const char* exceptionType;
- const char* exceptionMessage;
- jint bufferOffset = (jint)0;
- jint remaining;
- void* tempData;
-
- if (mBuffer) {
- tempData =
- (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
- if (remaining < size) {
- exception = 1;
- exceptionType = "java/lang/IllegalArgumentException";
- exceptionMessage = "remaining() < size < needed";
- goto exit;
- }
- }
- if (mBuffer && tempData == nullptr) {
- mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
- tempData = (void*)(mDataBase + bufferOffset);
- }
- return tempData;
- exit:
- if (mArray) {
- releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
- }
- if (exception) {
- jniThrowException(mEnv, exceptionType, exceptionMessage);
- }
- return nullptr;
- }
-
- JNIEnv* mEnv;
-
- // Java Buffer data
- void* mData;
- jobject mBuffer;
-
- // Indirect Buffer Data
- jarray mArray;
- char* mDataBase;
-};
-
-class MeshUniformBuilder {
-public:
- struct MeshUniform {
- template <typename T>
- std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
- const T& val) {
- if (!fVar) {
- LOG_FATAL("Assigning to missing variable");
- } else if (sizeof(val) != fVar->sizeInBytes()) {
- LOG_FATAL("Incorrect value size");
- } else {
- void* dst = reinterpret_cast<void*>(
- reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
- memcpy(dst, &val, sizeof(val));
- }
- }
-
- MeshUniform& operator=(const SkMatrix& val) {
- if (!fVar) {
- LOG_FATAL("Assigning to missing variable");
- } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
- LOG_FATAL("Incorrect value size");
- } else {
- float* data = reinterpret_cast<float*>(
- reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
- data[0] = val.get(0);
- data[1] = val.get(3);
- data[2] = val.get(6);
- data[3] = val.get(1);
- data[4] = val.get(4);
- data[5] = val.get(7);
- data[6] = val.get(2);
- data[7] = val.get(5);
- data[8] = val.get(8);
- }
- return *this;
- }
-
- template <typename T>
- bool set(const T val[], const int count) {
- static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
- if (!fVar) {
- LOG_FATAL("Assigning to missing variable");
- return false;
- } else if (sizeof(T) * count != fVar->sizeInBytes()) {
- LOG_FATAL("Incorrect value size");
- return false;
- } else {
- void* dst = reinterpret_cast<void*>(
- reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
- memcpy(dst, val, sizeof(T) * count);
- }
- return true;
- }
-
- MeshUniformBuilder* fOwner;
- const SkRuntimeEffect::Uniform* fVar;
- };
- MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
-
- explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
- fMeshSpec = sk_sp(meshSpec);
- fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
- }
-
- sk_sp<SkData> fUniforms;
-
-private:
- void* writableUniformData() {
- if (!fUniforms->unique()) {
- fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
- }
- return fUniforms->writable_data();
- }
-
- sk_sp<SkMeshSpecification> fMeshSpec;
-};
-
-struct MeshWrapper {
- SkMesh mesh;
- MeshUniformBuilder builder;
-};
-#endif // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp
index 3694ce0..a5e0476 100644
--- a/libs/hwui/jni/Path.cpp
+++ b/libs/hwui/jni/Path.cpp
@@ -182,11 +182,7 @@
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPathDirection dir = static_cast<SkPathDirection>(dirHandle);
AutoJavaFloatArray afa(env, array, 8);
-#ifdef SK_SCALAR_IS_FLOAT
const float* src = afa.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
obj->addRoundRect(rect, src, dir);
}
diff --git a/libs/hwui/jni/PathEffect.cpp b/libs/hwui/jni/PathEffect.cpp
index f99bef7..3dbe1a6 100644
--- a/libs/hwui/jni/PathEffect.cpp
+++ b/libs/hwui/jni/PathEffect.cpp
@@ -35,11 +35,7 @@
jfloatArray intervalArray, jfloat phase) {
AutoJavaFloatArray autoInterval(env, intervalArray);
int count = autoInterval.length() & ~1; // even number
-#ifdef SK_SCALAR_IS_FLOAT
- SkScalar* intervals = autoInterval.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
+ SkScalar* intervals = autoInterval.ptr();
SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release();
return reinterpret_cast<jlong>(effect);
}
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 8a0db1c..75d45e5 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -52,12 +52,7 @@
static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
{
AutoJavaFloatArray autoHSV(env, hsvArray, 3);
-#ifdef SK_SCALAR_IS_FLOAT
- SkScalar* hsv = autoHSV.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
-
+ SkScalar* hsv = autoHSV.ptr();
return static_cast<jint>(SkHSVToColor(alpha, hsv));
}
@@ -149,11 +144,7 @@
std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
AutoJavaFloatArray autoPos(env, posArray, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* pos = autoPos.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
@@ -193,11 +184,7 @@
std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
AutoJavaFloatArray autoPos(env, posArray, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* pos = autoPos.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
auto skTileMode = static_cast<SkTileMode>(tileMode);
@@ -225,11 +212,7 @@
std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
AutoJavaFloatArray autoPos(env, jpositions, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* pos = autoPos.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8a4d4e1..8ba7503 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -21,7 +21,6 @@
#else
#define __ANDROID_API_P__ 28
#endif
-#include <Mesh.h>
#include <androidfw/ResourceTypes.h>
#include <hwui/Canvas.h>
#include <hwui/Paint.h>
@@ -446,10 +445,10 @@
static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
jlong paintHandle) {
- const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh;
+ const Mesh* mesh = reinterpret_cast<Mesh*>(meshHandle);
SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
- SkPaint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint);
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ get_canvas(canvasHandle)->drawMesh(*mesh, SkBlender::Mode(blendMode), *paint);
}
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index cf6702e..ca667b0 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -23,8 +23,6 @@
static_assert(sizeof(SkMatrix) == 40, "Unexpected sizeof(SkMatrix), "
"update size in Matrix.java#NATIVE_ALLOCATION_SIZE and here");
-static_assert(SK_SCALAR_IS_FLOAT, "SK_SCALAR_IS_FLOAT is false, "
- "only float scalar is supported");
class SkMatrixGlue {
public:
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
new file mode 100644
index 0000000..04339dc
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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.
+ */
+
+#include <GrDirectContext.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+ ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect)
+ : mEnv(env), mBuffer(buffer) {
+ if (buffer == nullptr) {
+ mDataBase = nullptr;
+ mData = nullptr;
+ jniThrowNullPointerException(env);
+ } else {
+ mArray = (jarray) nullptr;
+ if (isDirect) {
+ mData = getDirectBufferPointer(mEnv, mBuffer);
+ } else {
+ mData = setIndirectData(size);
+ }
+ }
+ }
+
+ ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+ ~ScopedJavaNioBuffer() { reset(); }
+
+ void reset() {
+ if (mDataBase) {
+ releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+ mDataBase = nullptr;
+ }
+ }
+
+ ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+ if (this != &rhs) {
+ reset();
+
+ mEnv = rhs.mEnv;
+ mBuffer = rhs.mBuffer;
+ mDataBase = rhs.mDataBase;
+ mData = rhs.mData;
+ mArray = rhs.mArray;
+ rhs.mEnv = nullptr;
+ rhs.mData = nullptr;
+ rhs.mBuffer = nullptr;
+ rhs.mArray = nullptr;
+ rhs.mDataBase = nullptr;
+ }
+ return *this;
+ }
+
+ const void* data() const { return mData; }
+
+private:
+ /**
+ * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+ * from a java.nio.Buffer.
+ */
+ void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ if (pointer == 0) {
+ jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+ "Must use a native order direct Buffer");
+ return nullptr;
+ }
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+ }
+
+ static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+ env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+ }
+
+ static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+ jint* offset) {
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ *remaining = (limit - position) << elementSizeShift;
+ if (pointer != 0L) {
+ *array = nullptr;
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+ }
+
+ *array = jniGetNioBufferBaseArray(env, buffer);
+ *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+ return nullptr;
+ }
+
+ /**
+ * This is a copy of
+ * static void android_glBufferData__IILjava_nio_Buffer_2I
+ * from com_google_android_gles_jni_GLImpl.cpp
+ */
+ void* setIndirectData(size_t size) {
+ jint exception;
+ const char* exceptionType;
+ const char* exceptionMessage;
+ jint bufferOffset = (jint)0;
+ jint remaining;
+ void* tempData;
+
+ if (mBuffer) {
+ tempData =
+ (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+ if (remaining < size) {
+ exception = 1;
+ exceptionType = "java/lang/IllegalArgumentException";
+ exceptionMessage = "remaining() < size < needed";
+ goto exit;
+ }
+ }
+ if (mBuffer && tempData == nullptr) {
+ mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+ tempData = (void*)(mDataBase + bufferOffset);
+ }
+ return tempData;
+ exit:
+ if (mArray) {
+ releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+ }
+ if (exception) {
+ jniThrowException(mEnv, exceptionType, exceptionMessage);
+ }
+ return nullptr;
+ }
+
+ JNIEnv* mEnv;
+
+ // Java Buffer data
+ void* mData;
+ jobject mBuffer;
+
+ // Indirect Buffer Data
+ jarray mArray;
+ char* mDataBase;
+};
+
+namespace android {
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+ jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
+ jfloat right, jfloat bottom) {
+ auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+ size_t bufferSize = vertexCount * skMeshSpec->stride();
+ auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect);
+ auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+ auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset,
+ std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto [valid, msg] = meshPtr->validate();
+ if (!valid) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+ }
+ return reinterpret_cast<jlong>(meshPtr);
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+ jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+ jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+ jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
+ auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+ auto vertexBufferSize = vertexCount * skMeshSpec->stride();
+ auto indexBufferSize = indexCount * gIndexByteSize;
+ auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect);
+ auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect);
+ auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+ auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount,
+ vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset,
+ std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto [valid, msg] = meshPtr->validate();
+ if (!valid) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshPtr);
+}
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+ va_end(args);
+ return ret;
+}
+
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+ switch (type) {
+ case SkRuntimeEffect::Uniform::Type::kFloat:
+ case SkRuntimeEffect::Uniform::Type::kFloat2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4:
+ case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+ return false;
+ case SkRuntimeEffect::Uniform::Type::kInt:
+ case SkRuntimeEffect::Uniform::Type::kInt2:
+ case SkRuntimeEffect::Uniform::Type::kInt3:
+ case SkRuntimeEffect::Uniform::Type::kInt4:
+ return true;
+ }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+ const char* uniformName, const float values[], int count,
+ bool isColor) {
+ MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+ if (isColor) {
+ jniThrowExceptionFmt(
+ env, "java/lang/IllegalArgumentException",
+ "attempting to set a color uniform using the non-color specific APIs: %s %x",
+ uniformName, uniform.fVar->flags);
+ } else {
+ ThrowIAEFmt(env,
+ "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+ uniformName);
+ }
+ } else if (isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<float>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+ jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+ jint count) {
+ auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, uniformName);
+ const float values[4] = {value1, value2, value3, value4};
+ nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
+ wrapper->markDirty();
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
+ jfloatArray jvalues, jboolean isColor) {
+ auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, jUniformName);
+ AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+ nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+ autoValues.length(), isColor);
+ wrapper->markDirty();
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+ const char* uniformName, const int values[], int count) {
+ MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (!isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<int>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+ jint value1, jint value2, jint value3, jint value4, jint count) {
+ auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, uniformName);
+ const int values[4] = {value1, value2, value3, value4};
+ nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
+ wrapper->markDirty();
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+ jintArray values) {
+ auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaIntArray autoValues(env, values, 0);
+ nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+ autoValues.length());
+ wrapper->markDirty();
+}
+
+static void MeshWrapper_destroy(Mesh* wrapper) {
+ delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+ {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
+ {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
+ (void*)makeIndexed},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index fcfc4f8..af2d3b3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -23,6 +23,7 @@
#else
#include "DamageAccumulator.h"
#endif
+#include "TreeInfo.h"
#include "VectorDrawable.h"
#ifdef __ANDROID__
#include "renderthread/CanvasContext.h"
@@ -102,6 +103,12 @@
info.prepareTextures = false;
info.canvasContext.unpinImages();
}
+
+ auto grContext = info.canvasContext.getGrContext();
+ for (auto mesh : mMeshes) {
+ mesh->updateSkMesh(grContext);
+ }
+
#endif
bool hasBackwardProjectedNodesHere = false;
@@ -168,6 +175,7 @@
mDisplayList.reset();
+ mMeshes.clear();
mMutableImages.clear();
mVectorDrawables.clear();
mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 2a67734..7af31a4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -18,6 +18,7 @@
#include <deque>
+#include "Mesh.h"
#include "RecordingCanvas.h"
#include "RenderNodeDrawable.h"
#include "TreeInfo.h"
@@ -167,6 +168,7 @@
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<FunctorDrawable*> mChildFunctors;
std::vector<SkImage*> mMutableImages;
+ std::vector<const Mesh*> mMeshes;
private:
std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e1c8877..3ca7eeb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -321,6 +321,11 @@
return 0;
}
+void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+ mDisplayList->mMeshes.push_back(&mesh);
+ mRecorder.drawMesh(mesh, blender, paint);
+}
+
} // namespace skiapipeline
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 3fd8fa3..a8e4580 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -81,6 +81,7 @@
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
virtual void enableZ(bool enableZ) override;
+ virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 3d718a2..0e604ce 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -45,6 +45,8 @@
import androidx.credentials.CreateCredentialRequest.DisplayInfo
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.GetPasswordOption
+import androidx.credentials.GetPublicKeyCredentialOption
import java.time.Instant
@@ -71,7 +73,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testCreatePasswordRequestInfo()
+ ) ?: testCreatePasskeyRequestInfo()
val originName: String? = when (requestInfo.type) {
RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
@@ -120,8 +122,7 @@
providerDisableListUiState,
defaultProviderId,
requestDisplayInfoUiState,
- /** isOnPasskeyIntroStateAlready */
- false,
+ isOnPasskeyIntroStateAlready = false,
isPasskeyFirstUse
)!!,
getCredentialUiState = null,
@@ -400,7 +401,8 @@
" \"authenticatorSelection\": {\n" +
" \"residentKey\": \"required\",\n" +
" \"requireResidentKey\": true\n" +
- " }}"
+ " }}",
+ preferImmediatelyAvailableCredentials = true,
)
val credentialData = request.credentialData
return RequestInfo.newCreateRequestInfo(
@@ -446,19 +448,28 @@
}
private fun testGetRequestInfo(): RequestInfo {
+ val passwordOption = GetPasswordOption()
+ val passkeyOption = GetPublicKeyCredentialOption(
+ "json", preferImmediatelyAvailableCredentials = false)
return RequestInfo.newGetRequestInfo(
Binder(),
GetCredentialRequest.Builder(
Bundle()
).addCredentialOption(
CredentialOption(
- "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
- Bundle(),
- Bundle(), /*isSystemProviderRequired=*/
- false
+ passwordOption.type,
+ passwordOption.requestData,
+ passwordOption.candidateQueryData,
+ passwordOption.isSystemProviderRequired
)
- )
- .build(),
+ ).addCredentialOption(
+ CredentialOption(
+ passkeyOption.type,
+ passkeyOption.requestData,
+ passkeyOption.candidateQueryData,
+ passkeyOption.isSystemProviderRequired
+ )
+ ).build(),
"com.google.android.youtube"
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 9c9c2a3..a834994 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -50,7 +50,11 @@
import androidx.credentials.CreateCredentialRequest
import androidx.credentials.CreateCustomCredentialRequest
import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CredentialOption
import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.CreatePublicKeyCredentialRequestPrivileged
+import androidx.credentials.GetPublicKeyCredentialOption
+import androidx.credentials.GetPublicKeyCredentialOptionPrivileged
import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import androidx.credentials.provider.Action
import androidx.credentials.provider.AuthenticationAction
@@ -172,10 +176,27 @@
context: Context,
originName: String?,
): com.android.credentialmanager.getflow.RequestDisplayInfo? {
+ val getCredentialRequest = requestInfo.getCredentialRequest ?: return null
+ val preferImmediatelyAvailableCredentials = getCredentialRequest.credentialOptions.any {
+ val credentialOptionJetpack = CredentialOption.createFrom(
+ it.type,
+ it.credentialRetrievalData,
+ it.credentialRetrievalData,
+ it.isSystemProviderRequired
+ )
+ if (credentialOptionJetpack is GetPublicKeyCredentialOption) {
+ credentialOptionJetpack.preferImmediatelyAvailableCredentials
+ } else if (credentialOptionJetpack is GetPublicKeyCredentialOptionPrivileged) {
+ credentialOptionJetpack.preferImmediatelyAvailableCredentials
+ } else {
+ false
+ }
+ }
return com.android.credentialmanager.getflow.RequestDisplayInfo(
appName = originName
?: getAppLabel(context.packageManager, requestInfo.appPackageName)
- ?: return null
+ ?: return null,
+ preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
)
}
@@ -415,24 +436,25 @@
createCredentialRequestJetpack.password,
CredentialType.PASSWORD,
appLabel,
- context.getDrawable(R.drawable.ic_password)!!
+ context.getDrawable(R.drawable.ic_password) ?: return null,
+ preferImmediatelyAvailableCredentials = false,
)
is CreatePublicKeyCredentialRequest -> {
- val requestJson = createCredentialRequestJetpack.requestJson
- val json = JSONObject(requestJson)
- var name = ""
- var displayName = ""
- if (json.has("user")) {
- val user: JSONObject = json.getJSONObject("user")
- name = user.getString("name")
- displayName = user.getString("displayName")
- }
- RequestDisplayInfo(
- name,
- displayName,
- CredentialType.PASSKEY,
- appLabel,
- context.getDrawable(R.drawable.ic_passkey)!!
+ newRequestDisplayInfoFromPasskeyJson(
+ requestJson = createCredentialRequestJetpack.requestJson,
+ appLabel = appLabel,
+ context = context,
+ preferImmediatelyAvailableCredentials =
+ createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+ )
+ }
+ is CreatePublicKeyCredentialRequestPrivileged -> {
+ newRequestDisplayInfoFromPasskeyJson(
+ requestJson = createCredentialRequestJetpack.requestJson,
+ appLabel = appLabel,
+ context = context,
+ preferImmediatelyAvailableCredentials =
+ createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
)
}
is CreateCustomCredentialRequest -> {
@@ -446,7 +468,8 @@
type = CredentialType.UNKNOWN,
appName = appLabel,
typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_other_sign_in)!!
+ ?: context.getDrawable(R.drawable.ic_other_sign_in) ?: return null,
+ preferImmediatelyAvailableCredentials = false,
)
}
else -> null
@@ -613,5 +636,29 @@
)
} else null
}
+
+ private fun newRequestDisplayInfoFromPasskeyJson(
+ requestJson: String,
+ appLabel: String,
+ context: Context,
+ preferImmediatelyAvailableCredentials: Boolean,
+ ): RequestDisplayInfo? {
+ val json = JSONObject(requestJson)
+ var name = ""
+ var displayName = ""
+ if (json.has("user")) {
+ val user: JSONObject = json.getJSONObject("user")
+ name = user.getString("name")
+ displayName = user.getString("displayName")
+ }
+ return RequestDisplayInfo(
+ name,
+ displayName,
+ CredentialType.PASSKEY,
+ appLabel,
+ context.getDrawable(R.drawable.ic_passkey) ?: return null,
+ preferImmediatelyAvailableCredentials,
+ )
+ }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index ce4e936..192fa15 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -38,7 +38,9 @@
)
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
- return state.sortedCreateOptionsPairs.isNotEmpty()
+ return state.sortedCreateOptionsPairs.isNotEmpty() ||
+ (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials &&
+ state.remoteEntry != null)
}
open class ProviderInfo(
@@ -104,6 +106,7 @@
val type: CredentialType,
val appName: String,
val typeIcon: Drawable,
+ val preferImmediatelyAvailableCredentials: Boolean,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index d1f0f81..9727d3f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -37,7 +37,8 @@
internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean {
return state.providerDisplayInfo.sortedUserNameToCredentialEntryList.isNotEmpty() ||
state.providerDisplayInfo.authenticationEntryList.isNotEmpty() ||
- state.providerDisplayInfo.remoteEntry != null
+ (state.providerDisplayInfo.remoteEntry != null &&
+ !state.requestDisplayInfo.preferImmediatelyAvailableCredentials)
}
data class ProviderInfo(
@@ -146,6 +147,7 @@
data class RequestDisplayInfo(
val appName: String,
+ val preferImmediatelyAvailableCredentials: Boolean,
)
/**
@@ -246,7 +248,6 @@
private fun toGetScreenState(
providerDisplayInfo: ProviderDisplayInfo
): GetScreenState {
-
return if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
providerDisplayInfo.remoteEntry == null &&
providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty })
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 724588f..3fdb1d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -77,9 +77,8 @@
return true
}
- fun isEnabled(): Boolean {
- return getPageProvider(sppName)?.isEnabled(arguments) ?: false
- }
+ fun isEnabled(): Boolean =
+ SpaEnvironment.IS_DEBUG || getPageProvider(sppName)?.isEnabled(arguments) ?: false
fun getTitle(): String {
return getPageProvider(sppName)?.getTitle(arguments) ?: ""
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 02962a5..2d956d5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -88,4 +88,13 @@
open val sliceProviderAuthorities: String? = null
// TODO: add other environment setup here.
+ companion object {
+ /**
+ * Whether debug mode is on or off.
+ *
+ * If set to true, this will also enable all the pages under development (allows browsing
+ * and searching).
+ */
+ const val IS_DEBUG = false
+ }
}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 1ac20471..346462d 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -48,6 +48,7 @@
"test/**/*.java",
"src/android/provider/settings/backup/*",
"src/android/provider/settings/validators/*",
+ "src/com/android/providers/settings/GenerationRegistry.java",
"src/com/android/providers/settings/SettingsBackupAgent.java",
"src/com/android/providers/settings/SettingsState.java",
"src/com/android/providers/settings/SettingsHelper.java",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 5617331..7f3b0ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -17,19 +17,19 @@
package com.android.providers.settings;
import android.os.Bundle;
-import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.MemoryIntArray;
import android.util.Slog;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
/**
* This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates a shared memory region which
+ * on a per user basis and updates shared memory regions which
* client processes can read to determine if their local caches are
* stale.
*/
@@ -40,138 +40,187 @@
private final Object mLock;
+ // Key -> backingStore mapping
@GuardedBy("mLock")
- private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
+ private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
+
+ // Key -> (String->Index map) mapping
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ // Maximum number of backing stores allowed
+ static final int NUM_MAX_BACKING_STORE = 8;
@GuardedBy("mLock")
- private MemoryIntArray mBackingStore;
+ private int mNumBackingStore = 0;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ // Maximum size of an individual backing store
+ static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
public GenerationRegistry(Object lock) {
mLock = lock;
}
- public void incrementGeneration(int key) {
+ /**
+ * Increment the generation number if the setting is already cached in the backing stores.
+ * Otherwise, do nothing.
+ */
+ public void incrementGeneration(int key, String name) {
+ final boolean isConfig =
+ (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
+ // Only store the prefix if the mutated setting is a config
+ final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
synchronized (mLock) {
- MemoryIntArray backingStore = getBackingStoreLocked();
- if (backingStore != null) {
- try {
- final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
- if (index >= 0) {
- final int generation = backingStore.get(index) + 1;
- backingStore.set(index, generation);
- }
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error updating generation id", e);
- destroyBackingStore();
+ final MemoryIntArray backingStore = getBackingStoreLocked(key,
+ /* createIfNotExist= */ false);
+ if (backingStore == null) {
+ return;
+ }
+ try {
+ final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+ backingStore, /* createIfNotExist= */ false);
+ if (index < 0) {
+ return;
}
+ final int generation = backingStore.get(index) + 1;
+ backingStore.set(index, generation);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
+ + " key:" + SettingsState.keyToString(key)
+ + " at index:" + index);
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error updating generation id", e);
+ destroyBackingStoreLocked(key);
}
}
}
- public void addGenerationData(Bundle bundle, int key) {
+ /**
+ * Return the backing store's reference, the index and the current generation number
+ * of a cached setting. If it was not in the backing store, first create the entry in it before
+ * returning the result.
+ */
+ public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
synchronized (mLock) {
- MemoryIntArray backingStore = getBackingStoreLocked();
+ final MemoryIntArray backingStore = getBackingStoreLocked(key,
+ /* createIfNotExist= */ true);
+ if (backingStore == null) {
+ // Error accessing existing backing store or no new backing store is available
+ return;
+ }
try {
- if (backingStore != null) {
- final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
- if (index >= 0) {
- bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
- backingStore);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
- backingStore.get(index));
- if (DEBUG) {
- Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
- + SettingsProvider.keyToString(key));
- }
- }
+ final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+ backingStore, /* createIfNotExist= */ true);
+ if (index < 0) {
+ // Should not happen unless having error accessing the backing store
+ return;
+ }
+ bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+ backingStore);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+ backingStore.get(index));
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Exported index:" + index
+ + " for setting:" + indexMapKey
+ + " key:" + SettingsState.keyToString(key));
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error adding generation data", e);
- destroyBackingStore();
+ destroyBackingStoreLocked(key);
}
}
}
public void onUserRemoved(int userId) {
+ final int secureKey = SettingsState.makeKey(
+ SettingsState.SETTINGS_TYPE_SECURE, userId);
+ final int systemKey = SettingsState.makeKey(
+ SettingsState.SETTINGS_TYPE_SYSTEM, userId);
synchronized (mLock) {
- MemoryIntArray backingStore = getBackingStoreLocked();
- if (backingStore != null && mKeyToIndexMap.size() > 0) {
- try {
- final int secureKey = SettingsProvider.makeKey(
- SettingsProvider.SETTINGS_TYPE_SECURE, userId);
- resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
-
- final int systemKey = SettingsProvider.makeKey(
- SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
- resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error cleaning up for user", e);
- destroyBackingStore();
- }
+ if (mKeyToIndexMapMap.containsKey(secureKey)) {
+ destroyBackingStoreLocked(secureKey);
+ mKeyToIndexMapMap.remove(secureKey);
+ mNumBackingStore = mNumBackingStore - 1;
+ }
+ if (mKeyToIndexMapMap.containsKey(systemKey)) {
+ destroyBackingStoreLocked(systemKey);
+ mKeyToIndexMapMap.remove(systemKey);
+ mNumBackingStore = mNumBackingStore - 1;
}
}
}
@GuardedBy("mLock")
- private MemoryIntArray getBackingStoreLocked() {
- if (mBackingStore == null) {
- // One for the config table, one for the global table, two for system
- // and secure tables for a managed profile (managed profile is not
- // included in the max user count), ten for partially deleted users if
- // users are quickly removed, and twice max user count for system and
- // secure.
- final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
+ private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
+ MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+ if (!createIfNotExist) {
+ return backingStore;
+ }
+ if (backingStore == null) {
try {
- mBackingStore = new MemoryIntArray(size);
+ if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
+ Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+ return null;
+ }
+ backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
+ mKeyToBackingStoreMap.put(key, backingStore);
+ mNumBackingStore += 1;
if (DEBUG) {
- Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
+ Slog.e(LOG_TAG, "Created backing store for "
+ + SettingsState.keyToString(key) + " on user: "
+ + SettingsState.getUserIdFromKey(key));
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error creating generation tracker", e);
}
}
- return mBackingStore;
+ return backingStore;
}
- private void destroyBackingStore() {
- if (mBackingStore != null) {
+ @GuardedBy("mLock")
+ private void destroyBackingStoreLocked(int key) {
+ MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+ if (backingStore != null) {
try {
- mBackingStore.close();
+ backingStore.close();
if (DEBUG) {
- Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
+ Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Cannot close generation memory array", e);
}
- mBackingStore = null;
+ mKeyToBackingStoreMap.remove(key);
}
}
- private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
- MemoryIntArray backingStore) throws IOException {
- final int index = keyToIndexMap.get(key, -1);
- if (index >= 0) {
- keyToIndexMap.delete(key);
- backingStore.set(index, 0);
- if (DEBUG) {
- Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
- + SettingsProvider.keyToString(key));
+ private static int getKeyIndexLocked(int key, String indexMapKey,
+ ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
+ MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
+ ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
+ if (nameToIndexMap == null) {
+ if (!createIfNotExist) {
+ return -1;
}
+ nameToIndexMap = new ArrayMap<>();
+ keyToIndexMapMap.put(key, nameToIndexMap);
}
- }
-
- private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
- MemoryIntArray backingStore) throws IOException {
- int index = keyToIndexMap.get(key, -1);
+ int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
if (index < 0) {
+ if (!createIfNotExist) {
+ return -1;
+ }
index = findNextEmptyIndex(backingStore);
if (index >= 0) {
backingStore.set(index, 1);
- keyToIndexMap.append(key, index);
+ nameToIndexMap.put(indexMapKey, index);
if (DEBUG) {
- Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
- + SettingsProvider.keyToString(key));
+ Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
+ + " of type:" + SettingsState.keyToString(key)
+ + " on user:" + SettingsState.getUserIdFromKey(key));
}
} else {
Slog.e(LOG_TAG, "Could not allocate generation index");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 683c08e..ba275eb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.makeKey;
import android.Manifest;
import android.annotation.NonNull;
@@ -375,10 +376,6 @@
@GuardedBy("mLock")
private boolean mSyncConfigDisabledUntilReboot;
- public static int makeKey(int type, int userId) {
- return SettingsState.makeKey(type, userId);
- }
-
public static int getTypeFromKey(int key) {
return SettingsState.getTypeFromKey(key);
}
@@ -387,9 +384,6 @@
return SettingsState.getUserIdFromKey(key);
}
- public static String keyToString(int key) {
- return SettingsState.keyToString(key);
- }
@ChangeId
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -428,22 +422,26 @@
switch (method) {
case Settings.CALL_METHOD_GET_CONFIG: {
Setting setting = getConfigSetting(name);
- return packageValueForCallResult(setting, isTrackingGeneration(args));
+ return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId,
+ setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_GET_GLOBAL: {
Setting setting = getGlobalSetting(name);
- return packageValueForCallResult(setting, isTrackingGeneration(args));
+ return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId,
+ setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_GET_SECURE: {
Setting setting = getSecureSetting(name, requestingUserId);
- return packageValueForCallResult(setting, isTrackingGeneration(args));
+ return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId,
+ setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_GET_SYSTEM: {
Setting setting = getSystemSetting(name, requestingUserId);
- return packageValueForCallResult(setting, isTrackingGeneration(args));
+ return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId,
+ setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_PUT_CONFIG: {
@@ -553,7 +551,7 @@
case Settings.CALL_METHOD_LIST_CONFIG: {
String prefix = getSettingPrefix(args);
- Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
+ Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
isTrackingGeneration(args));
reportDeviceConfigAccess(prefix);
return result;
@@ -1319,6 +1317,7 @@
return false;
}
+ @NonNull
private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2316,7 +2315,8 @@
"get/set setting for user", null);
}
- private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
+ private Bundle packageValueForCallResult(int type, @NonNull String name, int userId,
+ @Nullable Setting setting, boolean trackingGeneration) {
if (!trackingGeneration) {
if (setting == null || setting.isNull()) {
return NULL_SETTING_BUNDLE;
@@ -2325,21 +2325,40 @@
}
Bundle result = new Bundle();
result.putString(Settings.NameValueTable.VALUE,
- !setting.isNull() ? setting.getValue() : null);
+ (setting != null && !setting.isNull()) ? setting.getValue() : null);
- mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
+ if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) {
+ // Don't track generation for non-existent settings unless the name is predefined
+ synchronized (mLock) {
+ mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
+ SettingsState.makeKey(type, userId), name);
+ }
+ }
return result;
}
- private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
- boolean trackingGeneration) {
+ private boolean isSettingPreDefined(String name, int type) {
+ if (type == SETTINGS_TYPE_GLOBAL) {
+ return sAllGlobalSettings.contains(name);
+ } else if (type == SETTINGS_TYPE_SECURE) {
+ return sAllSecureSettings.contains(name);
+ } else if (type == SETTINGS_TYPE_SYSTEM) {
+ return sAllSystemSettings.contains(name);
+ } else {
+ return false;
+ }
+ }
+
+ private Bundle packageValuesForCallResult(String prefix,
+ @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
Bundle result = new Bundle();
result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
if (trackingGeneration) {
+ // Track generation even if the namespace is empty because this is for system apps
synchronized (mLock) {
mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
- mSettingsRegistry.getSettingsLocked(
- SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
+ mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
+ UserHandle.USER_SYSTEM).mKey, prefix);
}
}
@@ -3449,7 +3468,7 @@
private void notifyForSettingsChange(int key, String name) {
// Increment the generation first, so observers always see the new value
- mGenerationRegistry.incrementGeneration(key);
+ mGenerationRegistry.incrementGeneration(key, name);
if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
final long token = Binder.clearCallingIdentity();
@@ -3487,7 +3506,7 @@
List<String> changedSettings) {
// Increment the generation first, so observers always see the new value
- mGenerationRegistry.incrementGeneration(key);
+ mGenerationRegistry.incrementGeneration(key, prefix);
StringBuilder stringBuilder = new StringBuilder(prefix);
for (int i = 0; i < changedSettings.size(); ++i) {
@@ -3513,7 +3532,7 @@
if (profileId != userId) {
final int key = makeKey(type, profileId);
// Increment the generation first, so observers always see the new value
- mGenerationRegistry.incrementGeneration(key);
+ mGenerationRegistry.incrementGeneration(key, name);
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
profileId, 0, uri).sendToTarget();
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
new file mode 100644
index 0000000..d34fe694
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.providers.settings;
+
+import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY;
+import static android.provider.Settings.CALL_METHOD_GENERATION_KEY;
+import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.util.MemoryIntArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class GenerationRegistryTest {
+ @Test
+ public void testGenerationsWithRegularSetting() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ // IncrementGeneration should have no effect on a non-cached setting.
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ // Default index is 0 and generation is only 1 despite early calls of incrementGeneration
+ checkBundle(b, 0, 1, false);
+
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ // Index is still 0 and generation is now 2; also check direct array access
+ assertThat(getArray(b).get(0)).isEqualTo(2);
+
+ final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0);
+ final String testSystemSetting = "test_system_setting";
+ generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+ // Default index is 0 and generation is 1 for another backingStore (system)
+ checkBundle(b, 0, 1, false);
+
+ final String testSystemSetting2 = "test_system_setting2";
+ generationRegistry.addGenerationData(b, systemKey, testSystemSetting2);
+ // Second system setting index is 1 and default generation is 1
+ checkBundle(b, 1, 1, false);
+
+ generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+ generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+ generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+ // First system setting generation now incremented to 3
+ checkBundle(b, 0, 3, false);
+
+ final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10);
+ generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+ // User 10 has a new set of backingStores
+ checkBundle(b, 0, 1, false);
+
+ // Check user removal
+ generationRegistry.onUserRemoved(10);
+ generationRegistry.incrementGeneration(systemKey2, testSystemSetting);
+
+ // Removed user should not affect existing caches
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ assertThat(getArray(b).get(0)).isEqualTo(2);
+
+ // IncrementGeneration should have no effect for a non-cached user
+ b.clear();
+ checkBundle(b, -1, -1, true);
+ // AddGeneration should create new backing store for the non-cached user
+ generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+ checkBundle(b, 0, 1, false);
+ }
+
+ @Test
+ public void testGenerationsWithConfigSetting() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final String prefix = "test_namespace/";
+ final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+ Bundle b = new Bundle();
+ generationRegistry.addGenerationData(b, configKey, prefix);
+ checkBundle(b, 0, 1, false);
+
+ final String setting = "test_namespace/test_setting";
+ // Check that the generation of the prefix is incremented correctly
+ generationRegistry.incrementGeneration(configKey, setting);
+ generationRegistry.addGenerationData(b, configKey, prefix);
+ checkBundle(b, 0, 2, false);
+ }
+
+ @Test
+ public void testMaxNumBackingStores() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
+ b.clear();
+ final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
+ generationRegistry.addGenerationData(b, key, testSecureSetting);
+ checkBundle(b, 0, 1, false);
+ }
+ b.clear();
+ final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
+ GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
+ generationRegistry.addGenerationData(b, key, testSecureSetting);
+ // Should fail to add generation because the number of backing stores has reached limit
+ checkBundle(b, -1, -1, true);
+ // Remove one user should free up a backing store
+ generationRegistry.onUserRemoved(0);
+ generationRegistry.addGenerationData(b, key, testSecureSetting);
+ checkBundle(b, 0, 1, false);
+ }
+
+ @Test
+ public void testMaxSizeBackingStore() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) {
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i);
+ checkBundle(b, i, 1, false);
+ }
+ b.clear();
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ // Should fail to increase index because the number of entries in the backing store has
+ // reached the limit
+ checkBundle(b, -1, -1, true);
+ // Shouldn't affect other cached entries
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0");
+ checkBundle(b, 0, 1, false);
+ }
+
+ private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
+ throws IOException {
+ final MemoryIntArray array = getArray(b);
+ if (isNull) {
+ assertThat(array).isNull();
+ } else {
+ assertThat(array).isNotNull();
+ }
+ final int index = b.getInt(
+ CALL_METHOD_GENERATION_INDEX_KEY, -1);
+ assertThat(index).isEqualTo(expectedIndex);
+ final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1);
+ assertThat(generation).isEqualTo(expectedGeneration);
+ if (!isNull) {
+ // Read into the result array with the result index should match the result generation
+ assertThat(array.get(index)).isEqualTo(generation);
+ }
+ }
+
+ private MemoryIntArray getArray(Bundle b) {
+ return b.getParcelable(
+ CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class);
+ }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d46961..b236ac5 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -449,6 +449,7 @@
enabled: true,
optimize: true,
shrink: true,
+ shrink_resources: true,
proguard_compatibility: false,
proguard_flags_files: ["proguard.flags"],
},
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 7d39c4a..dd60647 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -23,6 +23,7 @@
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
+import android.os.SystemProperties;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants;
@@ -115,6 +116,9 @@
public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
// Device dreaming state
public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
+ // Whether the back gesture is allowed (or ignored) by the Shade
+ public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
+ "persist.wm.debug.shade_allow_back_gesture", false);
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -243,9 +247,14 @@
sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
}
// Disable when in immersive, or the notifications are interactive
- int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN
- | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+ int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+
+ // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED.
+ // To allow Shade to respond to Back, we're bypassing this check (behind a flag).
+ if (!ALLOW_BACK_GESTURE_IN_SHADE) {
+ disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+ }
+
return (sysuiStateFlags & disableFlags) != 0;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd67115..f038c69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -29,6 +29,7 @@
import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -139,9 +140,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
+import com.android.settingslib.Utils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
@@ -152,6 +153,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -823,6 +825,19 @@
}
}
+ private void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ Assert.isMainThread();
+ Trace.beginSection("KeyGuardUpdateMonitor#onBiometricDetected");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricDetected(userId, biometricSourceType, isStrongBiometric);
+ }
+ }
+ Trace.endSection();
+ }
+
@VisibleForTesting
protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
Assert.isMainThread();
@@ -899,6 +914,20 @@
}
}
+ private void handleBiometricDetected(int authUserId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ Trace.beginSection("KeyGuardUpdateMonitor#handlerBiometricDetected");
+ onBiometricDetected(authUserId, biometricSourceType, isStrongBiometric);
+ if (biometricSourceType == FINGERPRINT) {
+ mLogger.logFingerprintDetected(authUserId, isStrongBiometric);
+ } else if (biometricSourceType == FACE) {
+ mLogger.logFaceDetected(authUserId, isStrongBiometric);
+ setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+ }
+
+ Trace.endSection();
+ }
+
private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -950,8 +979,14 @@
private void onFingerprintCancelNotReceived() {
mLogger.e("Fp cancellation not received, transitioning to STOPPED");
+ final boolean wasCancellingRestarting = mFingerprintRunningState
+ == BIOMETRIC_STATE_CANCELLING_RESTARTING;
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ if (wasCancellingRestarting) {
+ KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ } else {
+ KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ }
}
private void handleFingerprintError(int msgId, String errString) {
@@ -1038,6 +1073,12 @@
() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE),
getBiometricLockoutDelay());
} else {
+ boolean temporaryLockoutReset = wasLockout && !mFingerprintLockedOut;
+ if (temporaryLockoutReset) {
+ mLogger.d("temporaryLockoutReset - stopListeningForFingerprint() to stop"
+ + " detectFingerprint");
+ stopListeningForFingerprint();
+ }
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -1747,10 +1788,16 @@
}
};
+ private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback =
+ (sensorId, userId, isStrongBiometric) -> {
+ // Trigger the fingerprint detected path so the bouncer can be shown
+ handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric);
+ };
+
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
- // Trigger the face success path so the bouncer can be shown
- handleFaceAuthenticated(userId, isStrongBiometric);
+ // Trigger the face detected path so the bouncer can be shown
+ handleBiometricDetected(userId, FACE, isStrongBiometric);
};
@VisibleForTesting
@@ -2783,8 +2830,7 @@
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState
- && shouldListenSideFpsState
- && !isFingerprintLockedOut();
+ && shouldListenSideFpsState;
logListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
@@ -2843,8 +2889,7 @@
final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection
&& canBypass && !mPrimaryBouncerIsOrWillBeShowing
- && !isUserInLockdown(user)
- && !isFingerprintLockedOut();
+ && !isUserInLockdown(user);
final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
// If the face or fp has recently been authenticated do not attempt to authenticate again.
@@ -2940,11 +2985,7 @@
mLogger.v("startListeningForFingerprint - detect");
mFpm.detectFingerprint(
mFingerprintCancelSignal,
- (sensorId, user, isStrongBiometric) -> {
- mLogger.d("fingerprint detected");
- // Trigger the fingerprint success path so the bouncer can be shown
- handleFingerprintAuthenticated(user, isStrongBiometric);
- },
+ mFingerprintDetectionCallback,
new FingerprintAuthenticateOptions.Builder()
.setUserId(userId)
.build());
@@ -2984,6 +3025,14 @@
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
+ final FaceAuthenticateOptions faceAuthenticateOptions =
+ new SysUiFaceAuthenticateOptions(
+ userId,
+ faceAuthUiEvent,
+ faceAuthUiEvent == FACE_AUTH_UPDATED_STARTED_WAKING_UP
+ ? faceAuthUiEvent.getExtraInfo()
+ : WAKE_REASON_UNKNOWN
+ ).toFaceAuthenticateOptions();
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
@@ -2994,9 +3043,7 @@
// Run face detection. (If a face is detected, show the bouncer.)
mLogger.v("startListeningForFace - detect");
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback,
- new FaceAuthenticateOptions.Builder()
- .setUserId(userId)
- .build());
+ faceAuthenticateOptions);
} else {
// Don't run face detection. Instead, inform the user
// face auth is unavailable and how to proceed.
@@ -3015,7 +3062,8 @@
final boolean isBypassEnabled = mKeyguardBypassController != null
&& mKeyguardBypassController.isBypassEnabled();
mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
- mFaceAuthenticationCallback, null /* handler */, userId);
+ mFaceAuthenticationCallback, null /* handler */,
+ faceAuthenticateOptions);
}
setFaceRunningState(BIOMETRIC_STATE_RUNNING);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 38f3e50..0d4889a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -214,7 +214,7 @@
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { }
/**
- * Called when a biometric is recognized.
+ * Called when a biometric is authenticated.
* @param userId the user id for which the biometric sample was authenticated
* @param biometricSourceType
*/
@@ -222,6 +222,14 @@
boolean isStrongBiometric) { }
/**
+ * Called when a biometric is detected but not successfully authenticated.
+ * @param userId the user id for which the biometric sample was detected
+ * @param biometricSourceType
+ */
+ public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) { }
+
+ /**
* Called when biometric authentication provides help string (e.g. "Try again")
* @param msgId
* @param helpString
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index c414c08..e53f6ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -167,6 +167,20 @@
}, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
}
+ fun logFaceDetected(userId: Int, isStrongBiometric: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = userId
+ bool1 = isStrongBiometric
+ }, {"Face detected: userId: $int1, isStrongBiometric: $bool1"})
+ }
+
+ fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = userId
+ bool1 = isStrongBiometric
+ }, {"Fingerprint detected: userId: $int1, isStrongBiometric: $bool1"})
+ }
+
fun logFingerprintError(msgId: Int, originalErrMsg: String) {
logBuffer.log(TAG, DEBUG, {
str1 = originalErrMsg
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index e698faf..08efd89 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,6 +85,8 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -98,8 +100,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
-
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
@@ -872,7 +872,8 @@
}
/**
- * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}.
+ * Stores the callback received from
+ * {@link com.android.server.display.mode.DisplayModeDirector}.
*
* DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback}
* and registers it with this class by calling
diff --git a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
new file mode 100644
index 0000000..1390b4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+import android.content.ComponentName
+
+/** Returns true if the admin of [userId] disallows keyguard shortcuts. */
+fun DevicePolicyManager.areKeyguardShortcutsDisabled(
+ admin: ComponentName? = null,
+ userId: Int
+): Boolean {
+ val flags = getKeyguardDisabledFeatures(admin, userId)
+ return flags and KEYGUARD_DISABLE_SHORTCUTS_ALL == KEYGUARD_DISABLE_SHORTCUTS_ALL ||
+ flags and KEYGUARD_DISABLE_FEATURES_ALL == KEYGUARD_DISABLE_FEATURES_ALL
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2f44560..cbbd3e6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -64,9 +64,6 @@
// TODO(b/259130119): Tracking Bug
val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update")
- // TODO(b/265804648): Tracking Bug
- @JvmField val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi")
-
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
@@ -486,9 +483,9 @@
val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
- // TODO(b/255697805): Tracking Bug
+ // TODO(b/270987164): Tracking Bug
@JvmField
- val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false)
+ val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = true)
// TODO(b/263826204): Tracking Bug
@JvmField
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 dfbe1c2..9b5f7f6 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
@@ -26,6 +26,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -410,16 +411,10 @@
)
}
- private suspend fun isFeatureDisabledByDevicePolicy(): Boolean {
- val flags =
- withContext(backgroundDispatcher) {
- devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
- }
- val flagsToCheck =
- DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or
- DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
- return flagsToCheck and flags != 0
- }
+ private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
+ withContext(backgroundDispatcher) {
+ devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+ }
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
new file mode 100644
index 0000000..a79513e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.hardware.face.FaceAuthenticateOptions
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ASSISTANT_VISIBLE
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_UDFPS_POINTER_DOWN
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_UNKNOWN
+import android.hardware.face.FaceAuthenticateOptions.AuthenticateReason
+import android.os.PowerManager
+import android.os.PowerManager.WAKE_REASON_UNKNOWN
+import android.util.Log
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+
+/**
+ * Wrapper for [FaceAuthenticateOptions] to convert SystemUI values to their corresponding value in
+ * [FaceAuthenticateOptions].
+ */
+data class SysUiFaceAuthenticateOptions(
+ val userId: Int,
+ private val faceAuthUiEvent: UiEventLogger.UiEventEnum,
+ @PowerManager.WakeReason val wakeReason: Int = WAKE_REASON_UNKNOWN
+) {
+ val authenticateReason = setAuthenticateReason(faceAuthUiEvent)
+
+ /**
+ * The [FaceAuthUiEvent] for this operation. This method converts the UiEvent to the framework
+ * [AuthenticateReason].
+ */
+ @AuthenticateReason
+ fun setAuthenticateReason(uiEvent: UiEventLogger.UiEventEnum): Int {
+ return when (uiEvent) {
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP -> {
+ AUTHENTICATE_REASON_STARTED_WAKING_UP
+ }
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN -> {
+ AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN
+ }
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED -> {
+ AUTHENTICATE_REASON_ASSISTANT_VISIBLE
+ }
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN -> {
+ AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+ }
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED -> {
+ AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED
+ }
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED -> {
+ AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED
+ }
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED -> {
+ AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED
+ }
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER -> {
+ AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER
+ }
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN -> {
+ AUTHENTICATE_REASON_UDFPS_POINTER_DOWN
+ }
+ else -> {
+ Log.e("FaceAuthenticateOptions", " unmapped FaceAuthUiEvent $uiEvent")
+ AUTHENTICATE_REASON_UNKNOWN
+ }
+ }
+ }
+
+ /** Builds the instance. */
+ fun toFaceAuthenticateOptions(): FaceAuthenticateOptions {
+ return FaceAuthenticateOptions.Builder()
+ .setUserId(userId)
+ .setAuthenticateReason(authenticateReason)
+ .setWakeReason(wakeReason)
+ .build()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index f7b7db4..c65f0aa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.app.admin.DevicePolicyManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
@@ -27,7 +28,9 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
@@ -54,6 +57,8 @@
private val optionalUserManager: Optional<UserManager>,
private val optionalKeyguardManager: Optional<KeyguardManager>,
@NoteTaskEnabledKey private val isEnabled: Boolean,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val userTracker: UserTracker,
) {
@VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
@@ -107,11 +112,23 @@
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
+ val isKeyguardLocked = keyguardManager.isKeyguardLocked
+ // KeyguardQuickAffordanceInteractor blocks the quick affordance from showing in the
+ // keyguard if it is not allowed by the admin policy. Here we block any other way to show
+ // note task when the screen is locked.
+ if (
+ isKeyguardLocked &&
+ devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+ ) {
+ logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
+ return
+ }
+
val info =
resolver.resolveInfo(
entryPoint = entryPoint,
isInMultiWindowMode = isInMultiWindowMode,
- isKeyguardLocked = keyguardManager.isKeyguardLocked,
+ isKeyguardLocked = isKeyguardLocked,
)
?: return
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index b36f0d7..10e2afe 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -27,6 +27,7 @@
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.view.animation.DecelerateInterpolator;
@@ -41,7 +42,11 @@
public class ScrimDrawable extends Drawable {
private static final String TAG = "ScrimDrawable";
+ private boolean mShouldUseLargeScreenSize;
private final Paint mPaint;
+ private final Path mPath = new Path();
+ private final RectF mBoundsRectF = new RectF();
+
private int mAlpha = 255;
private int mMainColor;
private ValueAnimator mColorAnimation;
@@ -49,11 +54,13 @@
private float mCornerRadius;
private ConcaveInfo mConcaveInfo;
private int mBottomEdgePosition;
+ private float mBottomEdgeRadius = -1;
private boolean mCornerRadiusEnabled;
public ScrimDrawable() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
+ mShouldUseLargeScreenSize = false;
}
/**
@@ -133,6 +140,10 @@
return PixelFormat.TRANSLUCENT;
}
+ public void setShouldUseLargeScreenSize(boolean v) {
+ mShouldUseLargeScreenSize = v;
+ }
+
/**
* Corner radius used by either concave or convex corners.
*/
@@ -191,6 +202,10 @@
invalidateSelf();
}
+ public void setBottomEdgeRadius(float radius) {
+ mBottomEdgeRadius = radius;
+ }
+
@Override
public void draw(@NonNull Canvas canvas) {
mPaint.setColor(mMainColor);
@@ -198,9 +213,46 @@
if (mConcaveInfo != null) {
drawConcave(canvas);
} else if (mCornerRadiusEnabled && mCornerRadius > 0) {
- canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
- getBounds().bottom,
- /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
+ float topEdgeRadius = mCornerRadius;
+ float bottomEdgeRadius = mBottomEdgeRadius == -1.0 ? mCornerRadius : mBottomEdgeRadius;
+
+ mBoundsRectF.set(getBounds());
+
+ // When the back gesture causes the notification scrim to be scaled down,
+ // this offset "reveals" the rounded bottom edge as it "pulls away".
+ // We must *not* make this adjustment on largescreen shades (where the corner is sharp).
+ if (!mShouldUseLargeScreenSize && mBottomEdgeRadius != -1) {
+ mBoundsRectF.bottom -= bottomEdgeRadius;
+ }
+
+ // We need a box with rounded corners but its lower corners are not rounded on large
+ // screen devices in "portrait" orientation.
+ // Thus, we cannot draw a symmetric rounded rectangle via canvas.drawRoundRect()
+ // and must build a box with different corner radii at the top and at the bottom.
+ // Additionally, when the scrim is pushed to the very bottom of the screen, do not draw
+ // anything (drawing a rounded box with these specifications is not possible).
+ // TODO(b/271030611) perhaps this could be accomplished via Path.addRoundRect instead?
+ if (mBoundsRectF.bottom - mBoundsRectF.top > bottomEdgeRadius) {
+ mPath.reset();
+ mPath.moveTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius);
+ mPath.cubicTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius,
+ mBoundsRectF.right, mBoundsRectF.top,
+ mBoundsRectF.right - topEdgeRadius, mBoundsRectF.top);
+ mPath.lineTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top);
+ mPath.cubicTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top,
+ mBoundsRectF.left, mBoundsRectF.top,
+ mBoundsRectF.left, mBoundsRectF.top + topEdgeRadius);
+ mPath.lineTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius);
+ mPath.cubicTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius,
+ mBoundsRectF.left, mBoundsRectF.bottom,
+ mBoundsRectF.left + bottomEdgeRadius, mBoundsRectF.bottom);
+ mPath.lineTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom);
+ mPath.cubicTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom,
+ mBoundsRectF.right, mBoundsRectF.bottom,
+ mBoundsRectF.right, mBoundsRectF.bottom - bottomEdgeRadius);
+ mPath.close();
+ canvas.drawPath(mPath, mPaint);
+ }
} else {
canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
getBounds().bottom, mPaint);
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index f68e042..fc89a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
@@ -37,6 +38,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.util.LargeScreenUtils;
import java.util.concurrent.Executor;
@@ -102,6 +104,13 @@
@Override
protected void onDraw(Canvas canvas) {
if (mDrawable.getAlpha() > 0) {
+ Resources res = getResources();
+ // Scrim behind notification shade has sharp (not rounded) corners on large screens
+ // which scrim itself cannot know, so we set it here.
+ if (mDrawable instanceof ScrimDrawable) {
+ ((ScrimDrawable) mDrawable).setShouldUseLargeScreenSize(
+ LargeScreenUtils.shouldUseLargeScreenShadeHeader(res));
+ }
mDrawable.draw(canvas);
}
}
@@ -170,6 +179,15 @@
});
}
+ /**
+ * Set corner radius of the bottom edge of the Notification scrim.
+ */
+ public void setBottomEdgeRadius(float radius) {
+ if (mDrawable instanceof ScrimDrawable) {
+ ((ScrimDrawable) mDrawable).setBottomEdgeRadius(radius);
+ }
+ }
+
@VisibleForTesting
Drawable getDrawable() {
return mDrawable;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95206e5..e75320a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -293,7 +293,19 @@
* custom clock animation is in use.
*/
private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
+ /**
+ * Whether the Shade should animate to reflect Back gesture progress.
+ * To minimize latency at runtime, we cache this, else we'd be reading it every time
+ * updateQsExpansion() is called... and it's called very often.
+ *
+ * Whenever we change this flag, SysUI is restarted, so it's never going to be "stale".
+ */
+ public final boolean mAnimateBack;
+ /**
+ * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
+ */
+ public static final float SHADE_BACK_ANIM_MIN_SCALE = 0.9f;
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
private final Resources mResources;
private final KeyguardStateController mKeyguardStateController;
@@ -361,6 +373,8 @@
private CentralSurfaces mCentralSurfaces;
private HeadsUpManagerPhone mHeadsUpManager;
private float mExpandedHeight = 0;
+ /** The current squish amount for the predictive back animation */
+ private float mCurrentBackProgress = 0.0f;
private boolean mTracking;
private boolean mHintAnimationRunning;
private KeyguardBottomAreaView mKeyguardBottomArea;
@@ -815,6 +829,7 @@
mShadeHeaderController = shadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
+ mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -1951,6 +1966,14 @@
if (mFixedDuration != NO_FIXED_DURATION) {
animator.setDuration(mFixedDuration);
}
+
+ // Reset Predictive Back animation's transform after Shade is completely hidden.
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ resetBackTransformation();
+ }
+ });
}
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -2175,6 +2198,53 @@
}
}
+ /**
+ * When the back gesture triggers a fully-expanded shade --> QQS shade collapse transition,
+ * the expansionFraction goes down from 1.0 --> 0.0 (collapsing), so the current "squish" amount
+ * (mCurrentBackProgress) must be un-applied from various UI elements in tandem, such that,
+ * as the shade ends up in its half-expanded state (with QQS above), it is back at 100% scale.
+ * Without this, the shade would collapse, and stay squished.
+ */
+ public void adjustBackAnimationScale(float expansionFraction) {
+ if (expansionFraction > 0.0f) { // collapsing
+ float animatedFraction = expansionFraction * mCurrentBackProgress;
+ applyBackScaling(animatedFraction);
+ } else {
+ // collapsed! reset, so that if we re-expand shade, it won't start off "squished"
+ mCurrentBackProgress = 0;
+ }
+ }
+
+ //TODO(b/270981268): allow cancelling back animation mid-flight
+ /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+ public void onBackPressed() {
+ closeQsIfPossible();
+ }
+ /** Sets back progress. */
+ public void onBackProgressed(float progressFraction) {
+ // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out)
+ mCurrentBackProgress = progressFraction;
+ applyBackScaling(progressFraction);
+ }
+
+ /** Resets back progress. */
+ public void resetBackTransformation() {
+ mCurrentBackProgress = 0.0f;
+ applyBackScaling(0.0f);
+ }
+
+ /** Scales multiple elements in tandem to achieve the illusion of the QS+Shade shrinking
+ * as a single visual element (used by the Predictive Back Gesture preview animation).
+ * fraction = 0 implies "no scaling", and 1 means "scale down to minimum size (90%)".
+ */
+ public void applyBackScaling(float fraction) {
+ if (mNotificationContainerParent == null) {
+ return;
+ }
+ float scale = MathUtils.lerp(1.0f, SHADE_BACK_ANIM_MIN_SCALE, fraction);
+ mNotificationContainerParent.applyBackScaling(scale, mSplitShadeEnabled);
+ mScrimController.applyBackScaling(scale);
+ }
/** */
public float getLockscreenShadeDragProgress() {
// mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
@@ -4851,6 +4921,11 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack) {
+ // Cache the gesture insets now, so we can quickly query them during
+ // ACTION_MOVE and decide whether to intercept events for back gesture anim.
+ mQsController.updateGestureInsetsCache();
+ }
mShadeLog.logMotionEvent(event, "onTouch: down action");
startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
mMinExpandHeight = 0.0f;
@@ -4900,6 +4975,12 @@
}
break;
case MotionEvent.ACTION_MOVE:
+ // If the shade is half-collapsed, a horizontal swipe inwards from L/R edge
+ // must be routed to the back gesture (which shows a preview animation).
+ if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack
+ && mQsController.shouldBackBypassQuickSettings(x)) {
+ return false;
+ }
if (isFullyCollapsed()) {
// If panel is fully collapsed, reset haptic effect before adding movement.
mHasVibratedOnOpen = false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index f73dde6..7dff6ea 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
@@ -55,6 +56,13 @@
private QS mQs;
private View mQSContainer;
+ /**
+ * These are used to compute the bounding box containing the shade and the notification scrim,
+ * which is then used to drive the Back gesture animation.
+ */
+ private final Rect mUpperRect = new Rect();
+ private final Rect mBoundingBoxRect = new Rect();
+
@Nullable
private Consumer<Configuration> mConfigurationChangedListener;
@@ -172,4 +180,37 @@
public void applyConstraints(ConstraintSet constraintSet) {
constraintSet.applyTo(this);
}
+
+ /**
+ * Scale multiple elements in tandem, for the predictive back animation.
+ * This is how the Shade responds to the Back gesture (by scaling).
+ * Without the common center, individual elements will scale about their respective centers.
+ * Scaling the entire NotificationsQuickSettingsContainer will also resize the shade header
+ * (which we don't want).
+ */
+ public void applyBackScaling(float scale, boolean usingSplitShade) {
+ if (mStackScroller == null || mQSContainer == null) {
+ return;
+ }
+
+ mQSContainer.getBoundsOnScreen(mUpperRect);
+ mStackScroller.getBoundsOnScreen(mBoundingBoxRect);
+ mBoundingBoxRect.union(mUpperRect);
+
+ float cx = mBoundingBoxRect.centerX();
+ float cy = mBoundingBoxRect.centerY();
+
+ mQSContainer.setPivotX(cx);
+ mQSContainer.setPivotY(cy);
+ mQSContainer.setScaleX(scale);
+ mQSContainer.setScaleY(scale);
+
+ // When in large-screen split-shade mode, the notification stack scroller scales correctly
+ // only if the pivot point is at the left edge of the screen (because of its dimensions).
+ // When not in large-screen split-shade mode, we can scale correctly via the (cx,cy) above.
+ mStackScroller.setPivotX(usingSplitShade ? 0.0f : cx);
+ mStackScroller.setPivotY(cy);
+ mStackScroller.setScaleX(scale);
+ mStackScroller.setScaleY(scale);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 099ad94..6857f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -31,6 +31,7 @@
import android.animation.ValueAnimator;
import android.app.Fragment;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.Log;
@@ -40,6 +41,9 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
@@ -63,6 +67,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -83,10 +88,10 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.LargeScreenUtils;
-import javax.inject.Inject;
-
import dagger.Lazy;
+import javax.inject.Inject;
+
/** Handles QuickSettings touch handling, expansion and animation state
* TODO (b/264460656) make this dumpable
*/
@@ -223,6 +228,13 @@
private boolean mAnimatorExpand;
/**
+ * The gesture inset currently in effect -- used 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.
+ */
+ private Insets mCachedGestureInsets;
+
+ /**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade. This value can also go beyond 1.1 when we're overshooting!
@@ -406,6 +418,7 @@
mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
+ updateGestureInsetsCache();
}
// TODO (b/265054088): move this and others to a CoreStartable
@@ -469,6 +482,26 @@
|| touchX > mQsFrame.getX() + mQsFrame.getWidth();
}
+ /**
+ * 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() {
+ WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
+ WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
+ mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
+ WindowInsets.Type.systemGestures());
+ }
+
+ /**
+ * 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) {
+ return (touchX < mCachedGestureInsets.left)
+ || (touchX > mKeyguardStatusBar.getWidth() - mCachedGestureInsets.right);
+ }
+
/** Returns whether touch is within QS area */
private boolean isTouchInQsArea(float x, float y) {
if (isSplitShadeAndTouchXOutsideQs(x)) {
@@ -926,6 +959,10 @@
getHeaderTranslation(),
squishiness
);
+ if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE
+ && mPanelViewControllerLazy.get().mAnimateBack) {
+ mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction);
+ }
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -1113,6 +1150,7 @@
float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
Math.min(top / (float) mScrimCornerRadius, 1f));
+ mScrimController.setNotificationBottomRadius(radius);
}
if (isQsFragmentCreated()) {
float qsTranslation = 0;
@@ -1505,18 +1543,31 @@
}
private void handleDown(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
- mFalsingCollector.onQsDown();
- mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
- mTracking = true;
- onExpansionStarted();
- mInitialHeightOnTouch = mExpansionHeight;
- mInitialTouchY = event.getY();
- mInitialTouchX = event.getX();
- // TODO (b/265193930): remove dependency on NPVC
- // If we interrupt an expansion gesture here, make sure to update the state correctly.
- mPanelViewControllerLazy.get().notifyExpandingFinished();
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // When the shade is fully-expanded, an inward swipe from the L/R edge should first
+ // allow the back gesture's animation to preview the shade animation (if enabled).
+ // (swipes starting closer to the center of the screen will not be affected)
+ if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE
+ && mPanelViewControllerLazy.get().mAnimateBack) {
+ updateGestureInsetsCache();
+ if (shouldBackBypassQuickSettings(event.getX())) {
+ return;
+ }
+ }
+ if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
+ mFalsingCollector.onQsDown();
+ mShadeLog.logMotionEvent(event,
+ "handleQsDown: down action, QS tracking enabled");
+ mTracking = true;
+ onExpansionStarted();
+ mInitialHeightOnTouch = mExpansionHeight;
+ mInitialTouchY = event.getY();
+ mInitialTouchX = event.getX();
+ // TODO (b/265193930): remove dependency on NPVC
+ // If we interrupt an expansion gesture here, make sure to update the state
+ // correctly.
+ mPanelViewControllerLazy.get().notifyExpandingFinished();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index fc89be2..00d8c42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -28,10 +28,6 @@
val featureFlags: FeatureFlags,
val sysPropFlags: FlagResolver,
) {
- init {
- featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() }
- }
-
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
@@ -40,8 +36,6 @@
fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
- fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI)
-
fun forceDemoteFsi(): Boolean =
sysPropFlags.isEnabled(NotificationFlags.FSI_FORCE_DEMOTE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index ae19feb..9001470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -35,10 +35,6 @@
*/
NO_FSI_SHOW_STICKY_HUN(false),
/**
- * Full screen intents are disabled.
- */
- NO_FSI_DISABLED(false),
- /**
* No full screen intent included, so there is nothing to show.
*/
NO_FULL_SCREEN_INTENT(false),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 0163dbe..9f45b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -244,10 +244,6 @@
@Override
public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
- if (mFlags.disableFsi()) {
- return FullScreenIntentDecision.NO_FSI_DISABLED;
- }
-
if (entry.getSbn().getNotification().fullScreenIntent == null) {
if (entry.isStickyAndNotDemoted()) {
return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN;
@@ -343,9 +339,6 @@
case NO_FSI_SHOW_STICKY_HUN:
mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN");
return;
- case NO_FSI_DISABLED:
- mLogger.logNoFullscreen(entry, "Disabled");
- return;
case NO_FULL_SCREEN_INTENT:
return;
case NO_FSI_SUPPRESSED_BY_DND:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 9f38361..7855cdf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -374,6 +374,17 @@
}
@Override
+ public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ Trace.beginSection("BiometricUnlockController#onBiometricDetected");
+ if (mUpdateMonitor.isGoingToSleep()) {
+ Trace.endSection();
+ return;
+ }
+ startWakeAndUnlock(MODE_SHOW_BOUNCER);
+ }
+
+ @Override
public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index ae97407..b166446 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -104,6 +104,8 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.DateTimeView;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
@@ -508,6 +510,7 @@
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSliderController.Factory mBrightnessSliderFactory;
private final FeatureFlags mFeatureFlags;
+ private final boolean mAnimateBack;
private final FragmentService mFragmentService;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final WallpaperController mWallpaperController;
@@ -654,6 +657,7 @@
private final InteractionJankMonitor mJankMonitor;
+ /** Existing callback that handles back gesture invoked for the Shade. */
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
if (DEBUG) {
Log.d(TAG, "mOnBackInvokedCallback() called");
@@ -661,6 +665,33 @@
onBackPressed();
};
+ private boolean shouldBackBeHandled() {
+ return (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
+ && !isBouncerShowingOverDream());
+ }
+
+ /**
+ * New callback that handles back gesture invoked, cancel, progress
+ * and provides feedback via Shade animation.
+ * (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag)
+ */
+ private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
+ @Override
+ public void onBackInvoked() {
+ onBackPressed();
+ }
+
+ @Override
+ public void onBackProgressed(BackEvent event) {
+ if (shouldBackBeHandled()) {
+ if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+ float fraction = event.getProgress();
+ mNotificationPanelViewController.onBackProgressed(fraction);
+ }
+ }
+ }
+ };
+
/**
* Public constructor for CentralSurfaces.
*
@@ -882,6 +913,8 @@
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
+ // Based on teamfood flag, enable predictive back animation for the Shade.
+ mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
}
private void initBubbles(Bubbles bubbles) {
@@ -2706,7 +2739,8 @@
if (viewRootImpl != null) {
viewRootImpl.getOnBackInvokedDispatcher()
.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- mOnBackInvokedCallback);
+ mAnimateBack ? mOnBackAnimationCallback
+ : mOnBackInvokedCallback);
mIsBackCallbackRegistered = true;
if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered");
}
@@ -2721,7 +2755,9 @@
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.getOnBackInvokedDispatcher()
- .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ .unregisterOnBackInvokedCallback(
+ mAnimateBack ? mOnBackAnimationCallback
+ : mOnBackInvokedCallback);
mIsBackCallbackRegistered = false;
if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered");
}
@@ -3253,9 +3289,10 @@
if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
return true;
}
- if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
- && !isBouncerShowingOverDream()) {
+ if (shouldBackBeHandled()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+ // this is the Shade dismiss animation, so make sure QQS closes when it ends.
+ mNotificationPanelViewController.onBackPressed();
mShadeController.animateCollapseShade();
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8e0ec284..fb8bf52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -345,9 +345,16 @@
}
}
- /**
- * Sets corner radius of scrims.
- */
+ // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
+ /** Set corner radius of the bottom edge of the Notification scrim. */
+ public void setNotificationBottomRadius(float radius) {
+ if (mNotificationsScrim == null) {
+ return;
+ }
+ mNotificationsScrim.setBottomEdgeRadius(radius);
+ }
+
+ /** Sets corner radius of scrims. */
public void setScrimCornerRadius(int radius) {
if (mScrimBehind == null || mNotificationsScrim == null) {
return;
@@ -511,6 +518,12 @@
scheduleUpdate();
}
+ /** This is used by the predictive back gesture animation to scale the Shade. */
+ public void applyBackScaling(float scale) {
+ mNotificationsScrim.setScaleX(scale);
+ mNotificationsScrim.setScaleY(scale);
+ }
+
public void onTrackingStarted() {
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 462504e..4e27ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -82,6 +82,8 @@
fun startListener() {
handler.post {
if (hasStarted) return@post
+ logDebug { "Listener has started." }
+
hasStarted = true
isInUsiSession =
inputManager.hasInputDevice {
@@ -116,6 +118,10 @@
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+ logDebug {
+ "Stylus InputDevice added: $deviceId ${device.name}, " +
+ "External: ${device.isExternal}"
+ }
if (!device.isExternal) {
registerBatteryListener(deviceId)
@@ -137,6 +143,7 @@
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+ logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" }
val currAddress: String? = device.bluetoothAddress
val prevAddress: String? = inputDeviceAddressMap[deviceId]
@@ -157,6 +164,8 @@
if (!hasStarted) return
if (!inputDeviceAddressMap.contains(deviceId)) return
+ logDebug { "Stylus InputDevice removed: $deviceId" }
+
unregisterBatteryListener(deviceId)
val btAddress: String? = inputDeviceAddressMap[deviceId]
@@ -180,6 +189,11 @@
val isCharging = String(value) == "true"
+ logDebug {
+ "Charging state metadata changed for device $inputDeviceId " +
+ "${device.address}: $isCharging"
+ }
+
executeStylusBatteryCallbacks { cb ->
cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging)
}
@@ -194,13 +208,10 @@
handler.post {
if (!hasStarted) return@post
- if (DEBUG) {
- Log.d(
- TAG,
- "onBatteryStateChanged for $deviceId. " +
- "batteryState present: ${batteryState.isPresent}, " +
- "capacity: ${batteryState.capacity}"
- )
+ logDebug {
+ "Battery state changed for $deviceId. " +
+ "batteryState present: ${batteryState.isPresent}, " +
+ "capacity: ${batteryState.capacity}"
}
val batteryStateValid = isBatteryStateValid(batteryState)
@@ -216,7 +227,7 @@
}
private fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
- trackAndLogBluetoothSession(deviceId, true)
+ trackAndLogBluetoothSession(deviceId, btAddress, true)
val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
@@ -226,7 +237,7 @@
}
private fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {
- trackAndLogBluetoothSession(deviceId, false)
+ trackAndLogBluetoothSession(deviceId, btAddress, false)
val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.removeOnMetadataChangedListener(device, this)
@@ -245,6 +256,7 @@
if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
if (InputSettings.isStylusEverUsed(context)) return
+ logDebug { "Stylus used for the first time." }
InputSettings.setStylusEverUsed(context, true)
executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
}
@@ -259,12 +271,7 @@
// TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus
// is sent after the actual valid callback
if (batteryStateValid && usiSessionId == null) {
- if (DEBUG) {
- Log.d(
- TAG,
- "USI battery newly present, entering new USI session. Device ID: $deviceId"
- )
- }
+ logDebug { "USI battery newly present, entering new USI session: $deviceId" }
usiSessionId = instanceIdSequence.newInstanceId()
uiEventLogger.logWithInstanceId(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
@@ -273,9 +280,7 @@
usiSessionId
)
} else if (!batteryStateValid && usiSessionId != null) {
- if (DEBUG) {
- Log.d(TAG, "USI battery newly absent, exiting USI session Device ID: $deviceId")
- }
+ logDebug { "USI battery newly absent, exiting USI session: $deviceId" }
uiEventLogger.logWithInstanceId(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
0,
@@ -286,8 +291,17 @@
}
}
- private fun trackAndLogBluetoothSession(deviceId: Int, bluetoothConnected: Boolean) {
- if (bluetoothConnected) {
+ private fun trackAndLogBluetoothSession(
+ deviceId: Int,
+ btAddress: String,
+ btConnected: Boolean
+ ) {
+ logDebug {
+ "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" +
+ " $deviceId $btAddress"
+ }
+
+ if (btConnected) {
inputDeviceBtSessionIdMap[deviceId] = instanceIdSequence.newInstanceId()
uiEventLogger.logWithInstanceId(
StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED,
@@ -383,7 +397,13 @@
}
companion object {
- private val TAG = StylusManager::class.simpleName.orEmpty()
- private val DEBUG = false
+ val TAG = StylusManager::class.simpleName.orEmpty()
+ const val DEBUG = false
+ }
+}
+
+private inline fun logDebug(message: () -> String) {
+ if (StylusManager.DEBUG) {
+ Log.d(StylusManager.TAG, message())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 09b738f..f510768 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,9 +18,10 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN;
+import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -30,7 +31,6 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
-import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -82,6 +82,7 @@
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -633,20 +634,48 @@
@Test
public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
+ givenDetectFingerprintWithClearingFingerprintManagerInvocations();
verifyFingerprintAuthenticateNeverCalled();
verifyFingerprintDetectCall();
}
@Test
+ public void whenDetectFingerprint_biometricDetectCallback() {
+ ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
+ ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
+
+ givenDetectFingerprintWithClearingFingerprintManagerInvocations();
+ verify(mFingerprintManager).detectFingerprint(
+ any(), fpDetectCallbackCaptor.capture(), any());
+ fpDetectCallbackCaptor.getValue().onFingerprintDetected(0, 0, true);
+
+ // THEN verify keyguardUpdateMonitorCallback receives a detect callback
+ // and NO authenticate callbacks
+ verify(mTestCallback).onBiometricDetected(
+ eq(0), eq(BiometricSourceType.FINGERPRINT), eq(true));
+ verify(mTestCallback, never()).onBiometricAuthenticated(
+ anyInt(), any(), anyBoolean());
+ }
+
+ @Test
+ public void whenDetectFace_biometricDetectCallback() {
+ ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor =
+ ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class);
+
+ givenDetectFace();
+ verify(mFaceManager).detectFace(any(), faceDetectCallbackCaptor.capture(), any());
+ faceDetectCallbackCaptor.getValue().onFaceDetected(0, 0, false);
+
+ // THEN verify keyguardUpdateMonitorCallback receives a detect callback
+ // and NO authenticate callbacks
+ verify(mTestCallback).onBiometricDetected(
+ eq(0), eq(BiometricSourceType.FACE), eq(false));
+ verify(mTestCallback, never()).onBiometricAuthenticated(
+ anyInt(), any(), anyBoolean());
+ }
+
+ @Test
public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
// GIVEN unlocking with biometric is allowed
strongAuthNotRequired();
@@ -674,7 +703,7 @@
strongAuthNotRequired();
// WHEN fingerprint is locked out
- fingerprintErrorLockedOut();
+ fingerprintErrorTemporaryLockedOut();
// THEN unlocking with face is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -697,7 +726,7 @@
strongAuthNotRequired();
// WHEN fingerprint is locked out
- fingerprintErrorLockedOut();
+ fingerprintErrorTemporaryLockedOut();
// THEN unlocking with fingerprint is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -721,7 +750,7 @@
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
// WHEN fingerprint is locked out
- fingerprintErrorLockedOut();
+ fingerprintErrorTemporaryLockedOut();
// THEN user is NOT considered as "having trust" and bouncer cannot be skipped
Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
@@ -781,11 +810,6 @@
@Test
public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
- // GIVEN mocked keyguardUpdateMonitorCallback
- KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback =
- mock(KeyguardUpdateMonitorCallback.class);
- mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
-
// GIVEN bypass is enabled, face detection is supported
lockscreenBypassIsAllowed();
supportsFaceDetection();
@@ -804,22 +828,13 @@
verifyFaceAuthenticateNeverCalled();
// THEN biometric help message sent to callback
- verify(keyguardUpdateMonitorCallback).onBiometricHelp(
+ verify(mTestCallback).onBiometricHelp(
eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
}
@Test
public void faceDetect_whenStrongAuthRequiredAndBypass() {
- // GIVEN bypass is enabled, face detection is supported and strong auth is required
- lockscreenBypassIsAllowed();
- supportsFaceDetection();
- strongAuthRequiredEncrypted();
- keyguardIsVisible();
- // fingerprint is NOT running, UDFPS is NOT supported
-
- // WHEN the device wakes up
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
+ givenDetectFace();
// FACE detect is triggered, not authenticate
verifyFaceDetectCall();
@@ -836,39 +851,6 @@
}
@Test
- public void noFaceRun_whenFpLockout() {
- // GIVEN bypass is enabled, face detection is supported and strong auth is required
- lockscreenBypassIsAllowed();
- supportsFaceDetection();
- strongAuthRequiredEncrypted();
- keyguardIsVisible();
- // fingerprint is NOT running, UDFPS is NOT supported
-
- // GIVEN fp is locked out
- when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), anyInt()))
- .thenReturn(BIOMETRIC_LOCKOUT_TIMED);
- mKeyguardUpdateMonitor.handleUserSwitchComplete(0);
- assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(true);
-
- // WHEN the device wakes up
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
-
- // FACE detect is NOT triggered and face authenticate is NOT triggered
- verifyFaceDetectNeverCalled();
- verifyFaceAuthenticateNeverCalled();
-
- // WHEN bouncer becomes visible
- setKeyguardBouncerVisibility(true);
- clearInvocations(mFaceManager);
-
- // THEN face scanning is not run
- mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
- verifyFaceAuthenticateNeverCalled();
- verifyFaceDetectNeverCalled();
- }
-
- @Test
public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
// GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
lockscreenBypassIsAllowed();
@@ -1186,8 +1168,7 @@
// Fingerprint should be cancelled on lockout if going to lockout state, else
// restarted if it's not
assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
- .isEqualTo(fpLocked
- ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING);
+ .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
@Test
@@ -1646,7 +1627,7 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Fingerprint is locked out.
- fingerprintErrorLockedOut();
+ fingerprintErrorTemporaryLockedOut();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -2499,7 +2480,69 @@
eq(false));
}
+ @Test
+ public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() {
+ ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor =
+ ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class);
+ verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture());
+
+ // GIVEN device is locked out
+ fingerprintErrorTemporaryLockedOut();
+
+ // GIVEN FP detection is running
+ givenDetectFingerprintWithClearingFingerprintManagerInvocations();
+ verifyFingerprintDetectCall();
+ verifyFingerprintAuthenticateNeverCalled();
+
+ // WHEN temporary lockout resets
+ fpLockoutResetCallbackCaptor.getValue().onLockoutReset(0);
+ mTestableLooper.processAllMessages();
+
+ // THEN fingerprint detect state should cancel & then restart (for authenticate call)
+ assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+ .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
+ }
+
+ @Test
+ public void faceAuthenticateOptions_bouncerAuthenticateReason() {
+ // GIVEN the bouncer is fully visible
+ bouncerFullyVisible();
+
+ // WHEN authenticate is called
+ ArgumentCaptor<FaceAuthenticateOptions> captor =
+ ArgumentCaptor.forClass(FaceAuthenticateOptions.class);
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture());
+
+ // THEN the authenticate reason is attributed to the bouncer
+ assertThat(captor.getValue().getAuthenticateReason())
+ .isEqualTo(AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN);
+ }
+
+ @Test
+ public void faceAuthenticateOptions_wakingUpAuthenticateReason_powerButtonWakeReason() {
+ // GIVEN keyguard is visible
+ keyguardIsVisible();
+
+ // WHEN device wakes up from the power button
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth is triggered
+ ArgumentCaptor<FaceAuthenticateOptions> captor =
+ ArgumentCaptor.forClass(FaceAuthenticateOptions.class);
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture());
+
+ // THEN the authenticate reason is attributed to the waking
+ assertThat(captor.getValue().getAuthenticateReason())
+ .isEqualTo(AUTHENTICATE_REASON_STARTED_WAKING_UP);
+
+ // THEN the wake reason is attributed to the power button
+ assertThat(captor.getValue().getWakeReason())
+ .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
+ verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyInt(), anyInt());
}
@@ -2518,11 +2561,12 @@
}
private void verifyFaceAuthenticateNeverCalled() {
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
}
private void verifyFaceAuthenticateCall() {
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), any());
}
private void verifyFaceDetectNeverCalled() {
@@ -2602,7 +2646,7 @@
mKeyguardUpdateMonitor.setSwitchingUser(true);
}
- private void fingerprintErrorLockedOut() {
+ private void fingerprintErrorTemporaryLockedOut() {
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
}
@@ -2630,7 +2674,7 @@
any(),
mAuthenticationCallbackCaptor.capture(),
any(),
- anyInt());
+ any());
mAuthenticationCallbackCaptor.getValue()
.onAuthenticationSucceeded(
new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false));
@@ -2741,6 +2785,30 @@
receiver.setPendingResult(pendingResult);
}
+ private void givenDetectFingerprintWithClearingFingerprintManagerInvocations() {
+ // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+ // will trigger updateBiometricListeningState();
+ clearInvocations(mFingerprintManager);
+ mKeyguardUpdateMonitor.resetBiometricListeningState();
+
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+ mTestableLooper.processAllMessages();
+ }
+
+ private void givenDetectFace() {
+ // GIVEN bypass is enabled, face detection is supported and strong auth is required
+ lockscreenBypassIsAllowed();
+ supportsFaceDetection();
+ strongAuthRequiredEncrypted();
+ keyguardIsVisible();
+ // fingerprint is NOT running, UDFPS is NOT supported
+
+ // WHEN the device wakes up
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ }
+
private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
new file mode 100644
index 0000000..8203291
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class DevicePolicyManagerExtTest : SysuiTestCase() {
+
+ @Mock lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var userTracker: UserTracker
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(userTracker.userId).thenReturn(CURRENT_USER_ID)
+ }
+
+ // region areKeyguardShortcutsDisabled
+ @Test
+ fun areKeyguardShortcutsDisabled_noDisabledKeyguardFeature_shouldReturnFalse() {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE)
+
+ assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+ .isFalse()
+ }
+
+ @Test
+ fun areKeyguardShortcutsDisabled_otherDisabledKeyguardFeatures_shouldReturnFalse() {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_CAMERA or KEYGUARD_DISABLE_FACE)
+
+ assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+ .isFalse()
+ }
+
+ @Test
+ fun areKeyguardShortcutsDisabled_disabledShortcutsKeyguardFeature_shouldReturnTrue() {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+ assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+ .isTrue()
+ }
+
+ @Test
+ fun areKeyguardShortcutsDisabled_disabledAllKeyguardFeatures_shouldReturnTrue() {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_FEATURES_ALL)
+
+ assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+ .isTrue()
+ }
+ // endregion
+
+ private companion object {
+ const val CURRENT_USER_ID = 123
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 0f0b7d7..b872389 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,16 +16,18 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -38,6 +40,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@@ -55,6 +58,8 @@
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var userManager: UserManager
@Mock lateinit var eventLogger: NoteTaskEventLogger
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
@@ -65,6 +70,13 @@
whenever(context.packageManager).thenReturn(packageManager)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo)
whenever(userManager.isUserUnlocked).thenReturn(true)
+ whenever(
+ devicePolicyManager.getKeyguardDisabledFeatures(
+ /* admin= */ eq(null),
+ /* userHandle= */ anyInt()
+ )
+ )
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
}
private fun createNoteTaskController(
@@ -81,6 +93,8 @@
optionalUserManager = Optional.ofNullable(userManager),
optionalKeyguardManager = Optional.ofNullable(keyguardManager),
isEnabled = isEnabled,
+ devicePolicyManager = devicePolicyManager,
+ userTracker = userTracker,
)
// region onBubbleExpandChanged
@@ -384,6 +398,102 @@
}
// endregion
+ // region keyguard policy
+ @Test
+ fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ whenever(
+ devicePolicyManager.getKeyguardDisabledFeatures(
+ /* admin= */ eq(null),
+ /* userHandle= */ anyInt()
+ )
+ )
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+ )
+
+ verifyZeroInteractions(context, bubbles, eventLogger)
+ }
+
+ @Test
+ fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ whenever(
+ devicePolicyManager.getKeyguardDisabledFeatures(
+ /* admin= */ eq(null),
+ /* userHandle= */ anyInt()
+ )
+ )
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+ )
+
+ verifyZeroInteractions(context, bubbles, eventLogger)
+ }
+
+ @Test
+ fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+ whenever(
+ devicePolicyManager.getKeyguardDisabledFeatures(
+ /* admin= */ eq(null),
+ /* userHandle= */ anyInt()
+ )
+ )
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+ )
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+ assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ }
+
+ @Test
+ fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+ whenever(
+ devicePolicyManager.getKeyguardDisabledFeatures(
+ /* admin= */ eq(null),
+ /* userHandle= */ anyInt()
+ )
+ )
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+ )
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+ assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ }
+ // endregion
+
private companion object {
const val NOTES_PACKAGE_NAME = "com.android.note.app"
const val NOTES_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index abcde3d..d86ff67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -78,6 +78,25 @@
DejankUtils.setImmediate(true);
}
+ /**
+ * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f).
+ */
+ @Test
+ public void testBackGesture_min_scrimAtMaxScale() {
+ mNotificationPanelViewController.onBackProgressed(0.0f);
+ verify(mScrimController).applyBackScaling(1.0f);
+ }
+
+ /**
+ * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum.
+ */
+ @Test
+ public void testBackGesture_max_scrimAtMinScale() {
+ mNotificationPanelViewController.onBackProgressed(1.0f);
+ verify(mScrimController).applyBackScaling(
+ NotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
+ }
+
@Test
public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index ec294b1..68d67ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -517,6 +517,26 @@
verify(mVibratorHelper).vibrateAuthError(anyString());
}
+ @Test
+ public void onFingerprintDetect_showBouncer() {
+ // WHEN fingerprint detect occurs
+ mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
+ BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
+
+ // THEN shows primary bouncer
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ }
+
+ @Test
+ public void onFaceDetect_showBouncer() {
+ // WHEN face detect occurs
+ mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
+ BiometricSourceType.FACE, false /* isStrongBiometric */);
+
+ // THEN shows primary bouncer
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ }
+
private void givenFingerprintModeUnlockCollapsing() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index db4bb45..a168432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -77,6 +77,8 @@
import android.view.ViewGroup.LayoutParams;
import android.view.ViewRootImpl;
import android.view.WindowManager;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -182,6 +184,8 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -194,8 +198,6 @@
import java.io.PrintWriter;
import java.util.Optional;
-import dagger.Lazy;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -333,6 +335,10 @@
mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
// Set default value to avoid IllegalStateException.
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
+ // For the Shade to respond to Back gesture, we must enable the event routing
+ mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
+ // For the Shade to animate during the Back gesture, we must enable the animation flag.
+ mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -855,6 +861,50 @@
verify(mShadeController).animateCollapseShade();
}
+ /**
+ * When back progress is at 100%, the onBackProgressed animation driver inside
+ * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in).
+ */
+ @Test
+ public void testPredictiveBackAnimation_progressMaxScalesPanel() {
+ mCentralSurfaces.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
+ mCentralSurfaces.handleVisibleToUserChanged(true);
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ mOnBackInvokedCallback.capture());
+
+ OnBackAnimationCallback onBackAnimationCallback =
+ (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+ when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+
+ BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
+ onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
+ verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f));
+ }
+
+ /**
+ * When back progress is at 0%, the onBackProgressed animation driver inside
+ * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in).
+ */
+ @Test
+ public void testPredictiveBackAnimation_progressMinScalesPanel() {
+ mCentralSurfaces.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
+ mCentralSurfaces.handleVisibleToUserChanged(true);
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ mOnBackInvokedCallback.capture());
+
+ OnBackAnimationCallback onBackAnimationCallback =
+ (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+ when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+
+ BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
+ onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
+ verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f));
+ }
+
@Test
public void testPanelOpenForHeadsUp() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 7804ebf..864fe0f 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -22,8 +22,11 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.SensorDirectChannel;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SharedMemory;
import android.util.ArrayMap;
import android.util.Slog;
@@ -36,6 +39,7 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
public class SensorController {
@@ -47,6 +51,8 @@
private static final int UNKNOWN_ERROR = (-2147483647 - 1); // INT32_MIN value
private static final int BAD_VALUE = -22;
+ private static AtomicInteger sNextDirectChannelHandle = new AtomicInteger(1);
+
private final Object mLock;
private final int mVirtualDeviceId;
@GuardedBy("mLock")
@@ -57,8 +63,6 @@
private final SensorManagerInternal mSensorManagerInternal;
private final VirtualDeviceManagerInternal mVdmInternal;
-
-
public SensorController(@NonNull Object lock, int virtualDeviceId,
@Nullable IVirtualSensorCallback virtualSensorCallback) {
mLock = lock;
@@ -97,7 +101,7 @@
throws SensorCreationException {
final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
config.getType(), config.getName(),
- config.getVendor() == null ? "" : config.getVendor(),
+ config.getVendor() == null ? "" : config.getVendor(), config.getFlags(),
mRuntimeSensorCallback);
if (handle <= 0) {
throw new SensorCreationException("Received an invalid virtual sensor handle.");
@@ -212,6 +216,66 @@
}
return OK;
}
+
+ @Override
+ public int onDirectChannelCreated(ParcelFileDescriptor fd) {
+ if (mCallback == null) {
+ Slog.e(TAG, "No sensor callback for virtual deviceId " + mVirtualDeviceId);
+ return BAD_VALUE;
+ } else if (fd == null) {
+ Slog.e(TAG, "Received invalid ParcelFileDescriptor");
+ return BAD_VALUE;
+ }
+ final int channelHandle = sNextDirectChannelHandle.getAndIncrement();
+ SharedMemory sharedMemory = SharedMemory.fromFileDescriptor(fd);
+ try {
+ mCallback.onDirectChannelCreated(channelHandle, sharedMemory);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to call sensor callback: " + e);
+ return UNKNOWN_ERROR;
+ }
+ return channelHandle;
+ }
+
+ @Override
+ public void onDirectChannelDestroyed(int channelHandle) {
+ if (mCallback == null) {
+ Slog.e(TAG, "No sensor callback for virtual deviceId " + mVirtualDeviceId);
+ return;
+ }
+ try {
+ mCallback.onDirectChannelDestroyed(channelHandle);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to call sensor callback: " + e);
+ }
+ }
+
+ @Override
+ public int onDirectChannelConfigured(int channelHandle, int sensorHandle,
+ @SensorDirectChannel.RateLevel int rateLevel) {
+ if (mCallback == null) {
+ Slog.e(TAG, "No runtime sensor callback configured.");
+ return BAD_VALUE;
+ }
+ VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, sensorHandle);
+ if (sensor == null) {
+ Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId
+ + " and sensor handle=" + sensorHandle);
+ return BAD_VALUE;
+ }
+ try {
+ mCallback.onDirectChannelConfigured(channelHandle, sensor, rateLevel, sensorHandle);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to call sensor callback: " + e);
+ return UNKNOWN_ERROR;
+ }
+ if (rateLevel == SensorDirectChannel.RATE_STOP) {
+ return OK;
+ } else {
+ // Use the sensor handle as a report token, i.e. a unique identifier of the sensor.
+ return sensorHandle;
+ }
+ }
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f650560..642eaef 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -105,6 +105,14 @@
private static final String TAG = "VirtualDeviceImpl";
+ private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
@@ -281,7 +289,7 @@
* device.
*/
int getBaseVirtualDisplayFlags() {
- int flags = 0;
+ int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 10cd6ac..e9a7f20 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -192,20 +192,21 @@
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
- private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).setDeferUntilActive(true)
+ private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferUntilActive(true)
.toBundle();
- private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).setDeferUntilActive(true)
+ /** Used for both connected/disconnected, so match using key */
+ private Bundle mPowerOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
+ .setDeferUntilActive(true)
.toBundle();
- private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_POWER_CONNECTED)).setDeferUntilActive(true)
- .toBundle();
- private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_BATTERY_OKAY)).setDeferUntilActive(true)
- .toBundle();
- private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_BATTERY_LOW)).setDeferUntilActive(true)
+ /** Used for both low/okay, so match using key */
+ private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
+ .setDeferUntilActive(true)
.toBundle();
private MetricsLogger mMetricsLogger;
@@ -636,7 +637,7 @@
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
- mPowerConnectedOptions);
+ mPowerOptions);
}
});
}
@@ -648,7 +649,7 @@
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
- mPowerDisconnectedOptions);
+ mPowerOptions);
}
});
}
@@ -662,7 +663,7 @@
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
- mBatteryLowOptions);
+ mBatteryOptions);
}
});
} else if (mSentLowBatteryBroadcast &&
@@ -675,7 +676,7 @@
@Override
public void run() {
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
- mBatteryOkayOptions);
+ mBatteryOptions);
}
});
}
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 725ea5c..19e5cb1 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -79,6 +79,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;
@@ -105,6 +106,10 @@
// Size beyond which to force-compress newly added entries.
private static final long COMPRESS_THRESHOLD_BYTES = 16_384;
+ // Tags that we should drop by default.
+ private static final List<String> DISABLED_BY_DEFAULT_TAGS =
+ List.of("data_app_wtf", "system_app_wtf", "system_server_wtf");
+
// TODO: This implementation currently uses one file per entry, which is
// inefficient for smallish entries -- consider using a single queue file
// per tag (or even globally) instead.
@@ -549,8 +554,13 @@
public boolean isTagEnabled(String tag) {
final long token = Binder.clearCallingIdentity();
try {
- return !"disabled".equals(Settings.Global.getString(
+ if (DISABLED_BY_DEFAULT_TAGS.contains(tag)) {
+ return "enabled".equals(Settings.Global.getString(
mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+ } else {
+ return !"disabled".equals(Settings.Global.getString(
+ mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 70304c5..c1850bd 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -7822,7 +7822,7 @@
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
- // TODO(short-service): Log BFSL too.
+ // TODO(short-service): Log the UID capabilities (for BFSL) too, and also the procstate?
FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
r.appInfo.uid,
r.shortInstanceName,
@@ -7872,7 +7872,8 @@
r.mFgsNotificationShown ? 1 : 0,
durationMs,
r.mStartForegroundCount,
- fgsStopReasonToString(fgsStopReason));
+ fgsStopReasonToString(fgsStopReason),
+ r.foregroundServiceType);
}
private void updateNumForegroundServicesLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4a134ee..93effd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3537,7 +3537,7 @@
// We'll take the stack crawls of just the top apps using CPU.
final int workingStatsNumber = processCpuTracker.countWorkingStats();
- for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
+ for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
if (DEBUG_ANR) {
@@ -7015,36 +7015,6 @@
}
/**
- * Allows apps to retrieve the MIME type of a URI.
- * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
- * users, then it does not need permission to access the ContentProvider.
- * Either, it needs cross-user uri grants.
- *
- * CTS tests for this functionality can be run with "runtest cts-appsecurity".
- *
- * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
- * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
- *
- * @deprecated -- use getProviderMimeTypeAsync.
- */
- @Deprecated
- @Override
- public String getProviderMimeType(Uri uri, int userId) {
- return mCpHelper.getProviderMimeType(uri, userId);
- }
-
- /**
- * Allows apps to retrieve the MIME type of a URI.
- * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
- * users, then it does not need permission to access the ContentProvider.
- * Either way, it needs cross-user uri grants.
- */
- @Override
- public void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
- mCpHelper.getProviderMimeTypeAsync(uri, userId, resultCallback);
- }
-
- /**
* Filters calls to getType based on permission. If the caller has required permission,
* then it returns the contentProvider#getType.
* Else, it returns the contentProvider#getTypeAnonymous, which does not
@@ -18209,8 +18179,9 @@
bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
PowerExemptionManager.REASON_LOCALE_CHANGED, "");
- bOptions.setRemoveMatchingFilter(
- new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+ bOptions.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+ bOptions.setDeferUntilActive(true);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 72d6ca9..4c1835e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -146,6 +146,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
@@ -595,10 +596,14 @@
return 1;
}
- String mimeType = intent.getType();
- if (mimeType == null && intent.getData() != null
+ AtomicReference<String> mimeType = new AtomicReference<>(intent.getType());
+
+ if (mimeType.get() == null && intent.getData() != null
&& "content".equals(intent.getData().getScheme())) {
- mimeType = mInterface.getProviderMimeType(intent.getData(), mUserId);
+ mInterface.getMimeTypeFilterAsync(intent.getData(), mUserId,
+ new RemoteCallback(result -> {
+ mimeType.set(result.getPairValue());
+ }));
}
do {
@@ -611,8 +616,8 @@
int userIdForQuery = mInternal.mUserController.handleIncomingUser(
Binder.getCallingPid(), Binder.getCallingUid(), mUserId, false,
ALLOW_NON_FULL, "ActivityManagerShellCommand", null);
- List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
- userIdForQuery).getList();
+ List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType.get(),
+ 0, userIdForQuery).getList();
if (activities == null || activities.size() <= 0) {
getErrPrintWriter().println("Error: Intent does not match any activities: "
+ intent);
@@ -708,12 +713,12 @@
}
if (mWaitOption) {
result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
- mimeType, null, null, 0, mStartFlags, profilerInfo,
+ mimeType.get(), null, null, 0, mStartFlags, profilerInfo,
options != null ? options.toBundle() : null, mUserId);
res = result.result;
} else {
res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null,
- intent, mimeType, null, null, 0, mStartFlags, profilerInfo,
+ intent, mimeType.get(), null, null, 0, mStartFlags, profilerInfo,
options != null ? options.toBundle() : null, mUserId);
}
final long endTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b942f4b..841b61e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -597,18 +597,6 @@
final int cookie = traceBegin("enqueueBroadcast");
r.applySingletonPolicy(mService);
- final IntentFilter removeMatchingFilter = (r.options != null)
- ? r.options.getRemoveMatchingFilter() : null;
- if (removeMatchingFilter != null) {
- final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
- forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
- // We only allow caller to remove broadcasts they enqueued
- return (r.callingUid == testRecord.callingUid)
- && (r.userId == testRecord.userId)
- && removeMatching.test(testRecord.intent);
- }, mBroadcastConsumerSkipAndCanceled, true);
- }
-
applyDeliveryGroupPolicy(r);
r.enqueueTime = SystemClock.uptimeMillis();
@@ -909,6 +897,10 @@
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
try {
+ if (r.shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
+ UserHandle.getAppId(app.uid), r.callingUid, true);
+ }
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
thread.scheduleRegisteredReceiver(
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f721d69..48df1494 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -969,122 +969,6 @@
}
/**
- * Allows apps to retrieve the MIME type of a URI.
- * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
- * users, then it does not need permission to access the ContentProvider.
- * Either, it needs cross-user uri grants.
- *
- * CTS tests for this functionality can be run with "runtest cts-appsecurity".
- *
- * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
- * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
- *
- * @deprecated -- use getProviderMimeTypeAsync.
- */
- @Deprecated
- String getProviderMimeType(Uri uri, int userId) {
- mService.enforceNotIsolatedCaller("getProviderMimeType");
- final String name = uri.getAuthority();
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
- final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
- ? Binder.clearCallingIdentity() : 0;
- final ContentProviderHolder holder;
- try {
- holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
- "*getmimetype*", safeUserId);
- } finally {
- if (ident != 0) {
- Binder.restoreCallingIdentity(ident);
- }
- }
- try {
- if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
- final IBinder providerConnection = holder.connection;
- final ComponentName providerName = holder.info.getComponentName();
- // Note: creating a new Runnable instead of using a lambda here since lambdas in
- // java provide no guarantee that there will be a new instance returned every call.
- // Hence, it's possible that a cached copy is returned and the ANR is executed on
- // the incorrect provider.
- final Runnable providerNotResponding = new Runnable() {
- @Override
- public void run() {
- Log.w(TAG, "Provider " + providerName + " didn't return from getType().");
- appNotRespondingViaProvider(providerConnection);
- }
- };
- mService.mHandler.postDelayed(providerNotResponding, 1000);
- try {
- final String type = holder.provider.getType(uri);
- return type;
- } finally {
- mService.mHandler.removeCallbacks(providerNotResponding);
- // We need to clear the identity to call removeContentProviderExternalUnchecked
- final long token = Binder.clearCallingIdentity();
- try {
- removeContentProviderExternalUnchecked(name, null /* token */, safeUserId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Content provider dead retrieving " + uri, e);
- return null;
- } catch (Exception e) {
- Log.w(TAG, "Exception while determining type of " + uri, e);
- return null;
- }
-
- return null;
- }
-
- /**
- * Allows apps to retrieve the MIME type of a URI.
- * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
- * users, then it does not need permission to access the ContentProvider.
- * Either way, it needs cross-user uri grants.
- */
- void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
- mService.enforceNotIsolatedCaller("getProviderMimeTypeAsync");
- final String name = uri.getAuthority();
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
- final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
- ? Binder.clearCallingIdentity() : 0;
- final ContentProviderHolder holder;
- try {
- holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
- "*getmimetype*", safeUserId);
- } finally {
- if (ident != 0) {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- try {
- if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
- holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
- final long identity = Binder.clearCallingIdentity();
- try {
- removeContentProviderExternalUnchecked(name, null, safeUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- resultCallback.sendResult(result);
- }));
- } else {
- resultCallback.sendResult(Bundle.EMPTY);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Content provider dead retrieving " + uri, e);
- resultCallback.sendResult(Bundle.EMPTY);
- }
- }
-
- /**
* Filters calls to getType based on permission. If the caller has required permission,
* then it returns the contentProvider#getType.
* Else, it returns the contentProvider#getTypeAnonymous, which does not
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 1534ff5..50841ae 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -122,9 +122,9 @@
30091 um_user_visibility_changed (userId|1|5),(visible|1)
# Foreground service start/stop events.
-30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
-30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
-30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
+30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
+30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
+30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
# Intent Sender redirect for UserHandle.USER_CURRENT
30110 am_intent_sender_redirect_user (userId|1|5)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 0c36626..d05301a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -207,7 +207,8 @@
return AppProtoEnums.OOM_ADJ_REASON_PROCESS_BEGIN;
case OOM_ADJ_REASON_PROCESS_END:
return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
- case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: // TODO(short-service) add value to AppProtoEnums
+ case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+ return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
default:
return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 45181e8..0d0e576 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1315,7 +1315,7 @@
// persistent data
initVolumeGroupStates();
- mSoundDoseHelper.initSafeUsbMediaVolumeIndex();
+ mSoundDoseHelper.initSafeMediaVolumeIndex();
// Link VGS on VSS
initVolumeStreamStates();
@@ -8837,7 +8837,7 @@
final VolumeStreamState streamState = mStreamStates[update.mStreamType];
if (update.hasVolumeIndex()) {
int index = update.getVolumeIndex();
- if (!mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
+ if (mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
index = mSoundDoseHelper.safeMediaVolumeIndex(update.mDevice);
}
streamState.setIndex(index, update.mDevice, update.mCaller,
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 4b30234..cf81dbe 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -52,10 +52,9 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
+import java.util.HashMap;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@@ -80,7 +79,6 @@
// SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
// can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
// (when user opts out).
- // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED
private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed
@@ -94,6 +92,8 @@
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
+ private static final int MOMENTARY_EXPOSURE_TIMEOUT_MS = (20 * 3600 * 1000); // 20 hours
+
// 30s after boot completed
private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;
@@ -127,30 +127,50 @@
// mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
private int mSafeMediaVolumeIndex;
- // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
+ // mSafeMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
// property, divided by 100.0.
- private float mSafeUsbMediaVolumeDbfs;
+ // For now using the same value for CSD supported devices
+ private float mSafeMediaVolumeDbfs;
- // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
- // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
- // flinger mixer.
- // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
- // amplification when both effects are on with all band gains at maximum.
- // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
- // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
- private int mSafeUsbMediaVolumeIndex;
- // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
- private final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
- Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
- AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
+ private static class SafeDeviceVolumeInfo {
+ int mDeviceType;
+ int mSafeVolumeIndex = -1;
- private final Set<Integer> mSafeMediaCsdDevices = new HashSet<>(
- Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
- AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET,
- AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_BROADCAST,
- AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+ SafeDeviceVolumeInfo(int deviceType) {
+ mDeviceType = deviceType;
+ }
+ }
+
+ /**
+ * mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced.
+ * Contains a safe volume index for a given device type.
+ * Indexes are used for headsets and is the music volume UI index
+ * corresponding to a gain of mSafeMediaVolumeDbfs (defaulting to -37dB) in audio
+ * flinger mixer.
+ * We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+ * amplification when both effects are on with all band gains at maximum.
+ * This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
+ * the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
+ */
+ private final HashMap<Integer, SafeDeviceVolumeInfo> mSafeMediaVolumeDevices =
+ new HashMap<>() {{
+ put(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADSET));
+ put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE));
+ put(AudioSystem.DEVICE_OUT_USB_HEADSET,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_USB_HEADSET));
+ put(AudioSystem.DEVICE_OUT_BLE_HEADSET,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_HEADSET));
+ put(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_BROADCAST));
+ put(AudioSystem.DEVICE_OUT_HEARING_AID,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_HEARING_AID));
+ put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES));
+ put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+ }};
// mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
@@ -173,6 +193,10 @@
@GuardedBy("mCsdStateLock")
private float mCurrentCsd = 0.f;
+
+ @GuardedBy("mCsdStateLock")
+ private long mLastMomentaryExposureTimeMs = -1;
+
// dose at which the next dose reached warning occurs
@GuardedBy("mCsdStateLock")
private float mNextCsdWarning = 1.0f;
@@ -188,10 +212,26 @@
private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
public void onMomentaryExposure(float currentMel, int deviceId) {
+ if (!mEnableCsd) {
+ Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback");
+ return;
+ }
+
Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
+ currentMel);
mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
- if (mEnableCsd) {
+
+ boolean postWarning = false;
+ synchronized (mCsdStateLock) {
+ if (mLastMomentaryExposureTimeMs < 0
+ || (System.currentTimeMillis() - mLastMomentaryExposureTimeMs)
+ >= MOMENTARY_EXPOSURE_TIMEOUT_MS) {
+ mLastMomentaryExposureTimeMs = System.currentTimeMillis();
+ postWarning = true;
+ }
+ }
+
+ if (postWarning) {
mVolumeController.postDisplayCsdWarning(
AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE,
getTimeoutMsForWarning(AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE));
@@ -250,15 +290,11 @@
mContext = context;
mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
- if (mEnableCsd) {
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
- } else {
- mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
- Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
- }
-
initCsd();
+ mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
+
// The default safe volume index read here will be replaced by the actual value when
// the mcc is read by onConfigureSafeMedia()
// For now we use the same index for RS2 initial warning with CSD
@@ -388,14 +424,12 @@
}
/*package*/ int safeMediaVolumeIndex(int device) {
- if (!mSafeMediaVolumeDevices.contains(device)) {
+ final SafeDeviceVolumeInfo vi = mSafeMediaVolumeDevices.get(device);
+ if (vi == null) {
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
}
- if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
- return mSafeUsbMediaVolumeIndex;
- } else {
- return mSafeMediaVolumeIndex;
- }
+
+ return vi.mSafeVolumeIndex;
}
/*package*/ void restoreMusicActiveMs() {
@@ -419,20 +453,24 @@
/*package*/ void enforceSafeMediaVolume(String caller) {
AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
AudioSystem.STREAM_MUSIC);
- Set<Integer> devices = mSafeMediaVolumeDevices;
- for (int device : devices) {
- int index = streamState.getIndex(device);
- int safeIndex = safeMediaVolumeIndex(device);
+ for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
+ int index = streamState.getIndex(vi.mDeviceType);
+ int safeIndex = safeMediaVolumeIndex(vi.mDeviceType);
if (index > safeIndex) {
- streamState.setIndex(safeIndex, device, caller, true /*hasModifyAudioSettings*/);
+ streamState.setIndex(safeIndex, vi.mDeviceType, caller,
+ true /*hasModifyAudioSettings*/);
mAudioHandler.sendMessageAtTime(
- mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, device, /*arg2=*/0,
- streamState), /*delay=*/0);
+ mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, vi.mDeviceType,
+ /*arg2=*/0, streamState), /*delay=*/0);
}
}
}
+ /**
+ * Returns {@code true} if the safe media actions can be applied for the given stream type,
+ * volume index and device.
+ **/
/*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) {
boolean result;
synchronized (mSafeMediaVolumeStateLock) {
@@ -443,17 +481,16 @@
@GuardedBy("mSafeMediaVolumeStateLock")
private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
- return (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_ACTIVE)
- || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
- || (!mSafeMediaVolumeDevices.contains(device))
- || (index <= safeMediaVolumeIndex(device))
- || mEnableCsd;
+ return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
+ && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
+ && (mSafeMediaVolumeDevices.containsKey(device))
+ && (index > safeMediaVolumeIndex(device));
}
/*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
int flags) {
synchronized (mSafeMediaVolumeStateLock) {
- if (!checkSafeMediaVolume_l(streamType, index, device)) {
+ if (checkSafeMediaVolume_l(streamType, index, device)) {
mVolumeController.postDisplaySafeVolumeWarning(flags);
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
@@ -484,15 +521,13 @@
/*package*/ void scheduleMusicActiveCheck() {
synchronized (mSafeMediaVolumeStateLock) {
cancelMusicActiveCheck();
- if (!mEnableCsd) {
- mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
- REQUEST_CODE_CHECK_MUSIC_ACTIVE,
- new Intent(ACTION_CHECK_MUSIC_ACTIVE),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime()
- + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
- }
+ mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+ new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime()
+ + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
}
}
@@ -500,7 +535,7 @@
synchronized (mSafeMediaVolumeStateLock) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
- if (mSafeMediaVolumeDevices.contains(device) && isStreamActive) {
+ if (mSafeMediaVolumeDevices.containsKey(device) && isStreamActive) {
scheduleMusicActiveCheck();
int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
device);
@@ -528,27 +563,31 @@
/*package*/ void configureSafeMedia(boolean forced, String caller) {
int msg = MSG_CONFIGURE_SAFE_MEDIA;
- mAudioHandler.removeMessages(msg);
+ if (forced) {
+ // unforced should not cancel forced configure messages
+ mAudioHandler.removeMessages(msg);
+ }
long time = 0;
if (forced) {
time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean(
"audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS));
}
+
mAudioHandler.sendMessageAtTime(
mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
time);
}
- /*package*/ void initSafeUsbMediaVolumeIndex() {
- // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
- // relies on audio policy having correct ranges for volume indexes.
- mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+ /*package*/ void initSafeMediaVolumeIndex() {
+ for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
+ vi.mSafeVolumeIndex = getSafeDeviceMediaVolumeIndex(vi.mDeviceType);
+ }
}
/*package*/ int getSafeMediaVolumeIndex(int device) {
- if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains(
- device)) {
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE
+ && mSafeMediaVolumeDevices.containsKey(device)) {
return safeMediaVolumeIndex(device);
} else {
return -1;
@@ -557,7 +596,7 @@
/*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device,
int flags) {
- if (checkSafeMediaVolume(streamType, index, device)) {
+ if (!checkSafeMediaVolume(streamType, index, device)) {
return false;
}
@@ -566,7 +605,7 @@
}
/*package*/ boolean safeDevicesContains(int device) {
- return mSafeMediaVolumeDevices.contains(device);
+ return mSafeMediaVolumeDevices.containsKey(device);
}
/*package*/ void invalidatPendingVolumeCommand() {
@@ -612,8 +651,11 @@
pw.print(" mSafeMediaVolumeState=");
pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
- pw.print(" mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
- pw.print(" mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
+ for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
+ pw.print(" mSafeMediaVolumeIndex["); pw.print(vi.mDeviceType);
+ pw.print("]="); pw.println(vi.mSafeVolumeIndex);
+ }
+ pw.print(" mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs);
pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
pw.print(" mMcc="); pw.println(mMcc);
pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
@@ -660,11 +702,12 @@
if (!isAbsoluteVolume) {
// remove any possible previous attenuation
soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
+
return;
}
if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
- && mSafeMediaCsdDevices.contains(device)) {
+ && mSafeMediaVolumeDevices.containsKey(device)) {
soundDose.updateAttenuation(
AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
(newIndex + 5) / 10,
@@ -715,7 +758,7 @@
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
- mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+ initSafeMediaVolumeIndex();
boolean safeMediaVolumeEnabled =
SystemProperties.getBoolean("audio.safemedia.force", false)
@@ -728,7 +771,7 @@
// The persisted state is either "disabled" or "active": this is the state applied
// next time we boot and cannot be "inactive"
int persistedState;
- if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) {
+ if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
// The state can already be "inactive" here if the user has forced it before
// the 30 seconds timeout for forced configuration. In this case we don't reset
@@ -801,25 +844,32 @@
mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
}
- private int getSafeUsbMediaVolumeIndex() {
+ private int getSafeDeviceMediaVolumeIndex(int deviceType) {
+ // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP
+ // instead of computing it from the volume curves
+ if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd) {
+ return mSafeMediaVolumeIndex;
+ }
+
// determine UI volume index corresponding to the wanted safe gain in dBFS
int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
- mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
+ mSafeMediaVolumeDbfs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
while (Math.abs(max - min) > 1) {
int index = (max + min) / 2;
- float gainDB = AudioSystem.getStreamVolumeDB(
- AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
+ float gainDB = AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, index,
+ deviceType);
if (Float.isNaN(gainDB)) {
//keep last min in case of read error
break;
- } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
+ } else if (gainDB == mSafeMediaVolumeDbfs) {
min = index;
break;
- } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
+ } else if (gainDB < mSafeMediaVolumeDbfs) {
min = index;
} else {
max = index;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 8a33f22..f6c1375 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -427,13 +427,6 @@
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, options.getUserId())) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 6e1640d..22b6a53 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -35,7 +35,6 @@
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
@@ -126,7 +125,7 @@
private static final int MSG_BRIGHTNESS_CHANGED = 1;
private static final int MSG_STOP_SENSOR_LISTENER = 2;
private static final int MSG_START_SENSOR_LISTENER = 3;
- private static final int MSG_BRIGHTNESS_CONFIG_CHANGED = 4;
+ private static final int MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED = 4;
private static final int MSG_SENSOR_CHANGED = 5;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -162,7 +161,7 @@
private boolean mColorSamplingEnabled;
private int mNoFramesToSample;
private float mFrameRate;
- private BrightnessConfiguration mBrightnessConfiguration;
+ private boolean mShouldCollectColorSample = false;
// End of block of members that should only be accessed on the mBgHandler thread.
private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL;
@@ -208,9 +207,9 @@
/**
* Update tracker with new brightness configuration.
*/
- public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration) {
- mBgHandler.obtainMessage(MSG_BRIGHTNESS_CONFIG_CHANGED,
- brightnessConfiguration).sendToTarget();
+ public void setShouldCollectColorSample(boolean shouldCollectColorSample) {
+ mBgHandler.obtainMessage(MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED,
+ shouldCollectColorSample).sendToTarget();
}
private void backgroundStart(float initialBrightness) {
@@ -320,7 +319,7 @@
* Notify the BrightnessTracker that the user has changed the brightness of the display.
*/
public void notifyBrightnessChanged(float brightness, boolean userInitiated,
- float powerBrightnessFactor, boolean isUserSetBrightness,
+ float powerBrightnessFactor, boolean wasShortTermModelActive,
boolean isDefaultBrightnessConfig, String uniqueDisplayId, float[] luxValues,
long[] luxTimestamps) {
if (DEBUG) {
@@ -329,7 +328,7 @@
}
Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
- powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
+ powerBrightnessFactor, wasShortTermModelActive, isDefaultBrightnessConfig,
mInjector.currentTimeMillis(), uniqueDisplayId, luxValues, luxTimestamps));
m.sendToTarget();
}
@@ -343,7 +342,7 @@
}
private void handleBrightnessChanged(float brightness, boolean userInitiated,
- float powerBrightnessFactor, boolean isUserSetBrightness,
+ float powerBrightnessFactor, boolean wasShortTermModelActive,
boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId,
float[] luxValues, long[] luxTimestamps) {
BrightnessChangeEvent.Builder builder;
@@ -368,7 +367,7 @@
builder.setBrightness(brightness);
builder.setTimeStamp(timestamp);
builder.setPowerBrightnessFactor(powerBrightnessFactor);
- builder.setUserBrightnessPoint(isUserSetBrightness);
+ builder.setUserBrightnessPoint(wasShortTermModelActive);
builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
builder.setUniqueDisplayId(uniqueDisplayId);
@@ -827,8 +826,7 @@
if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
|| !mInjector.isInteractive(mContext)
|| mColorSamplingEnabled
- || mBrightnessConfiguration == null
- || !mBrightnessConfiguration.shouldCollectColorSamples()) {
+ || !mShouldCollectColorSample) {
return;
}
@@ -997,7 +995,7 @@
BrightnessChangeValues values = (BrightnessChangeValues) msg.obj;
boolean userInitiatedChange = (msg.arg1 == 1);
handleBrightnessChanged(values.brightness, userInitiatedChange,
- values.powerBrightnessFactor, values.isUserSetBrightness,
+ values.powerBrightnessFactor, values.wasShortTermModelActive,
values.isDefaultBrightnessConfig, values.timestamp,
values.uniqueDisplayId, values.luxValues, values.luxTimestamps);
break;
@@ -1009,14 +1007,11 @@
stopSensorListener();
disableColorSampling();
break;
- case MSG_BRIGHTNESS_CONFIG_CHANGED:
- mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
- boolean shouldCollectColorSamples =
- mBrightnessConfiguration != null
- && mBrightnessConfiguration.shouldCollectColorSamples();
- if (shouldCollectColorSamples && !mColorSamplingEnabled) {
+ case MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED:
+ mShouldCollectColorSample = (boolean) msg.obj;
+ if (mShouldCollectColorSample && !mColorSamplingEnabled) {
enableColorSampling();
- } else if (!shouldCollectColorSamples && mColorSamplingEnabled) {
+ } else if (!mShouldCollectColorSample && mColorSamplingEnabled) {
disableColorSampling();
}
break;
@@ -1031,7 +1026,7 @@
private static class BrightnessChangeValues {
public final float brightness;
public final float powerBrightnessFactor;
- public final boolean isUserSetBrightness;
+ public final boolean wasShortTermModelActive;
public final boolean isDefaultBrightnessConfig;
public final long timestamp;
public final String uniqueDisplayId;
@@ -1039,11 +1034,11 @@
public final long[] luxTimestamps;
BrightnessChangeValues(float brightness, float powerBrightnessFactor,
- boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
+ boolean wasShortTermModelActive, boolean isDefaultBrightnessConfig,
long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) {
this.brightness = brightness;
this.powerBrightnessFactor = powerBrightnessFactor;
- this.isUserSetBrightness = isUserSetBrightness;
+ this.wasShortTermModelActive = wasShortTermModelActive;
this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
this.timestamp = timestamp;
this.uniqueDisplayId = uniqueDisplayId;
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 99e709e..7b560ce 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -27,6 +27,8 @@
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.server.display.mode.DisplayModeDirector;
+
import java.io.PrintWriter;
/**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d9b3501..75f8acc 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1260,7 +1260,7 @@
return mAmbientDarkeningPercentagesIdle;
}
- SensorData getAmbientLightSensor() {
+ public SensorData getAmbientLightSensor() {
return mAmbientLightSensor;
}
@@ -2821,7 +2821,7 @@
/**
* Uniquely identifies a Sensor, with the combination of Type and Name.
*/
- static class SensorData {
+ public static class SensorData {
public String type;
public String name;
public float minRefreshRate = 0.0f;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6eb465e..214c591 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -152,6 +152,7 @@
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayDeviceConfig.SensorData;
import com.android.server.display.layout.Layout;
+import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.wm.SurfaceAnimationThread;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9917bfc..da8e6a6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1538,10 +1538,10 @@
// user, or is a temporary adjustment.
boolean userInitiatedChange = (Float.isNaN(brightnessState))
&& (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
- boolean hadUserBrightnessPoint = false;
+ boolean wasShortTermModelActive = false;
// Configure auto-brightness.
if (mAutomaticBrightnessController != null) {
- hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+ wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
mAutomaticBrightnessController.configure(autoBrightnessState,
mBrightnessConfiguration,
mLastUserSetScreenBrightness,
@@ -1555,7 +1555,8 @@
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
if (mBrightnessTracker != null) {
- mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+ mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
+ && mBrightnessConfiguration.shouldCollectColorSamples());
}
boolean updateScreenBrightnessSetting = false;
@@ -1823,7 +1824,7 @@
userInitiatedChange = false;
}
notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
- hadUserBrightnessPoint);
+ wasShortTermModelActive);
}
// We save the brightness info *after* the brightness setting has been changed and
@@ -1865,7 +1866,7 @@
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
- mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
+ mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -2634,7 +2635,7 @@
}
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
- boolean hadUserDataPoint) {
+ boolean wasShortTermModelActive) {
final float brightnessInNits = convertToNits(brightness);
if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
@@ -2645,7 +2646,7 @@
? mPowerRequest.screenLowPowerBrightnessFactor
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
- powerFactor, hadUserDataPoint,
+ powerFactor, wasShortTermModelActive,
mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
mAutomaticBrightnessController.getLastSensorValues(),
mAutomaticBrightnessController.getLastSensorTimestamps());
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index f49419cf..a2a53e3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1250,10 +1250,10 @@
// user, or is a temporary adjustment.
boolean userInitiatedChange = (Float.isNaN(brightnessState))
&& (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
- boolean hadUserBrightnessPoint = false;
+ boolean wasShortTermModelActive = false;
// Configure auto-brightness.
if (mAutomaticBrightnessController != null) {
- hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+ wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
mAutomaticBrightnessController.configure(autoBrightnessState,
mBrightnessConfiguration,
mDisplayBrightnessController.getLastUserSetScreenBrightness(),
@@ -1267,7 +1267,8 @@
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
if (mBrightnessTracker != null) {
- mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+ mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
+ && mBrightnessConfiguration.shouldCollectColorSamples());
}
boolean updateScreenBrightnessSetting = false;
@@ -1537,7 +1538,7 @@
userInitiatedChange = false;
}
notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
- hadUserBrightnessPoint);
+ wasShortTermModelActive);
}
// We save the brightness info *after* the brightness setting has been changed and
@@ -1579,7 +1580,7 @@
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
- mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
+ mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
.getDisplayBrightnessStrategyName());
// Temporary is what we use during slider interactions. We avoid logging those so that
@@ -2220,7 +2221,7 @@
}
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
- boolean hadUserDataPoint) {
+ boolean wasShortTermModelActive) {
final float brightnessInNits = convertToNits(brightness);
if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
@@ -2231,7 +2232,7 @@
? mPowerRequest.screenLowPowerBrightnessFactor
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
- powerFactor, hadUserDataPoint,
+ powerFactor, wasShortTermModelActive,
mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
mAutomaticBrightnessController.getLastSensorValues(),
mAutomaticBrightnessController.getLastSensorTimestamps());
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8f52c97..58c5034 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -46,6 +46,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index fc90db6..78c597e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -33,6 +33,7 @@
import android.view.SurfaceControl;
import com.android.server.display.layout.Layout;
+import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 3e67f0a..2ce7690 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -36,6 +36,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.mode.DisplayModeDirector;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
similarity index 98%
rename from services/core/java/com/android/server/display/DisplayModeDirector.java
rename to services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 31f5ab7..24d5ca4 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.display;
+package com.android.server.display.mode;
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
@@ -67,6 +67,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
+import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.utils.AmbientFilter;
import com.android.server.display.utils.AmbientFilterFactory;
import com.android.server.display.utils.SensorUtils;
@@ -214,6 +215,9 @@
mUdfpsObserver.observe();
}
+ /**
+ * Enables or disables component logging
+ */
public void setLoggingEnabled(boolean loggingEnabled) {
if (mLoggingEnabled == loggingEnabled) {
return;
@@ -1574,7 +1578,10 @@
}
}
- final class AppRequestObserver {
+ /**
+ * Responsible for keeping track of app requested refresh rates per display
+ */
+ public final class AppRequestObserver {
private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
@@ -1583,6 +1590,9 @@
mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
}
+ /**
+ * Sets refresh rates from app request
+ */
public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange,
float requestedMaxRefreshRateRange) {
synchronized (mLock) {
@@ -1665,7 +1675,7 @@
return null;
}
- public void dumpLocked(PrintWriter pw) {
+ private void dumpLocked(PrintWriter pw) {
pw.println(" AppRequestObserver");
pw.println(" mAppRequestedModeByDisplay:");
for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
@@ -1794,7 +1804,7 @@
*/
@VisibleForTesting
public class BrightnessObserver implements DisplayManager.DisplayListener {
- private final static int LIGHT_SENSOR_RATE_MS = 250;
+ private static final int LIGHT_SENSOR_RATE_MS = 250;
private int[] mLowDisplayBrightnessThresholds;
private int[] mLowAmbientBrightnessThresholds;
private int[] mHighDisplayBrightnessThresholds;
@@ -2019,7 +2029,7 @@
return mLowAmbientBrightnessThresholds;
}
- public void observe(SensorManager sensorManager) {
+ private void observe(SensorManager sensorManager) {
mSensorManager = sensorManager;
mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
@@ -2064,11 +2074,11 @@
mDeviceConfigDisplaySettings.startListening();
mInjector.registerDisplayListener(this, mHandler,
- DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
- DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
}
- public void setLoggingEnabled(boolean loggingEnabled) {
+ private void setLoggingEnabled(boolean loggingEnabled) {
if (mLoggingEnabled == loggingEnabled) {
return;
}
@@ -2076,7 +2086,8 @@
mLightSensorListener.setLoggingEnabled(loggingEnabled);
}
- public void onRefreshRateSettingChangedLocked(float min, float max) {
+ @VisibleForTesting
+ void onRefreshRateSettingChangedLocked(float min, float max) {
boolean changeable = (max - min > 1f && max > 60f);
if (mRefreshRateChangeable != changeable) {
mRefreshRateChangeable = changeable;
@@ -2089,14 +2100,14 @@
}
}
- public void onLowPowerModeEnabledLocked(boolean b) {
+ private void onLowPowerModeEnabledLocked(boolean b) {
if (mLowPowerModeEnabled != b) {
mLowPowerModeEnabled = b;
updateSensorStatus();
}
}
- public void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
+ private void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
int[] ambientThresholds) {
if (displayThresholds != null && ambientThresholds != null
&& displayThresholds.length == ambientThresholds.length) {
@@ -2123,7 +2134,7 @@
}
}
- public void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
+ private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
int[] ambientThresholds) {
if (displayThresholds != null && ambientThresholds != null
&& displayThresholds.length == ambientThresholds.length) {
@@ -2150,7 +2161,7 @@
}
}
- public void dumpLocked(PrintWriter pw) {
+ void dumpLocked(PrintWriter pw) {
pw.println(" BrightnessObserver");
pw.println(" mAmbientLux: " + mAmbientLux);
pw.println(" mBrightness: " + mBrightness);
@@ -2400,7 +2411,7 @@
}
@VisibleForTesting
- public void setDefaultDisplayState(int state) {
+ void setDefaultDisplayState(int state) {
if (mLoggingEnabled) {
Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = "
+ mDefaultDisplayState + ", state = " + state);
@@ -2475,7 +2486,7 @@
}
private final class LightSensorEventListener implements SensorEventListener {
- final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
+ private static final int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
private float mLastSensorData;
private long mTimestamp;
private boolean mLoggingEnabled;
@@ -2876,10 +2887,10 @@
}
final int hbmMode = info.highBrightnessMode;
- final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF &&
- info.adjustedBrightness > info.highBrightnessTransitionPoint;
- if (hbmMode == mHbmMode.get(displayId) &&
- isHbmActive == mHbmActive.get(displayId)) {
+ final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+ && info.adjustedBrightness > info.highBrightnessTransitionPoint;
+ if (hbmMode == mHbmMode.get(displayId)
+ && isHbmActive == mHbmActive.get(displayId)) {
// no change, ignore.
return;
}
@@ -2901,7 +2912,7 @@
Vote vote = null;
if (mHbmActive.get(displayId, false)) {
final int hbmMode =
- mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+ mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
// Device resource properties take priority over DisplayDeviceConfig
if (mRefreshRateInHbmSunlight > 0) {
@@ -2909,7 +2920,7 @@
mRefreshRateInHbmSunlight);
} else {
final List<RefreshRateLimitation> limits =
- mDisplayManagerInternal.getRefreshRateLimitations(displayId);
+ mDisplayManagerInternal.getRefreshRateLimitations(displayId);
for (int i = 0; limits != null && i < limits.size(); i++) {
final RefreshRateLimitation limitation = limits.get(i);
if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
@@ -2919,8 +2930,8 @@
}
}
}
- } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR &&
- mRefreshRateInHbmHdr > 0) {
+ } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ && mRefreshRateInHbmHdr > 0) {
// HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for
// a vote from Device properties
vote = Vote.forPhysicalRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
@@ -2988,9 +2999,6 @@
}
private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
- public DeviceConfigDisplaySettings() {
- }
-
public void startListening() {
mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(), this);
@@ -3001,8 +3009,8 @@
*/
public int[] getLowDisplayBrightnessThresholds() {
return getIntArrayProperty(
- DisplayManager.DeviceConfig.
- KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+ DisplayManager.DeviceConfig
+ .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
}
/*
@@ -3010,8 +3018,8 @@
*/
public int[] getLowAmbientBrightnessThresholds() {
return getIntArrayProperty(
- DisplayManager.DeviceConfig.
- KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+ DisplayManager.DeviceConfig
+ .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
}
public int getRefreshRateInLowZone() {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0c69855..5a832b7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1065,18 +1065,7 @@
mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsHave");
}
- private static final String[] UNPROTECTED_SETTINGS = {
- // These three LOCK_PATTERN_* settings have traditionally been readable via the public API
- // android.provider.Settings.{System,Secure}.getString() without any permission.
- Settings.Secure.LOCK_PATTERN_ENABLED,
- Settings.Secure.LOCK_PATTERN_VISIBLE,
- Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED,
- };
-
private final void checkDatabaseReadPermission(String requestedKey, int userId) {
- if (ArrayUtils.contains(UNPROTECTED_SETTINGS, requestedKey)) {
- return;
- }
if (!hasPermission(PERMISSION)) {
throw new SecurityException("uid=" + getCallingUid() + " needs permission "
+ PERMISSION + " to read " + requestedKey + " for user " + userId);
@@ -1190,9 +1179,6 @@
@Override
public boolean getBoolean(String key, boolean defaultValue, int userId) {
checkDatabaseReadPermission(key, userId);
- if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
- return getCredentialTypeInternal(userId) == CREDENTIAL_TYPE_PATTERN;
- }
return mStorage.getBoolean(key, defaultValue, userId);
}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
new file mode 100644
index 0000000..58fdb57
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
+import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
+import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaRoute2Info;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controls bluetooth routes and provides selected route override.
+ *
+ * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does
+ * not support routes selection logic. Instead, relies on external clients to make a decision
+ * about currently selected route.
+ *
+ * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio
+ * Policies.
+ */
+class AudioPoliciesBluetoothRouteController implements BluetoothRouteController {
+ private static final String TAG = "APBtRouteController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
+ private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
+
+ // Maps hardware address to BluetoothRouteInfo
+ private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+ private final List<BluetoothRouteInfo> mActiveRoutes = new ArrayList<>();
+
+ // Route type -> volume map
+ private final SparseIntArray mVolumeMap = new SparseIntArray();
+
+ private final Context mContext;
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final BluetoothRoutesUpdatedListener mListener;
+ private final AudioManager mAudioManager;
+ private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+ private final AdapterStateChangedReceiver mAdapterStateChangedReceiver =
+ new AdapterStateChangedReceiver();
+ private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
+ new DeviceStateChangedReceiver();
+
+ private BluetoothA2dp mA2dpProfile;
+ private BluetoothHearingAid mHearingAidProfile;
+ private BluetoothLeAudio mLeAudioProfile;
+
+ AudioPoliciesBluetoothRouteController(Context context, BluetoothAdapter btAdapter,
+ BluetoothRoutesUpdatedListener listener) {
+ mContext = context;
+ mBluetoothAdapter = btAdapter;
+ mListener = listener;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ buildBluetoothRoutes();
+ }
+
+ /**
+ * Registers listener to bluetooth status changes as the provided user.
+ *
+ * The registered receiver listens to {@link BluetoothA2dp#ACTION_ACTIVE_DEVICE_CHANGED} and
+ * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED } events for {@link BluetoothProfile#A2DP},
+ * {@link BluetoothProfile#HEARING_AID}, and {@link BluetoothProfile#LE_AUDIO} bluetooth profiles.
+ *
+ * @param user {@code UserHandle} as which receiver is registered
+ */
+ @Override
+ public void start(UserHandle user) {
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
+
+ IntentFilter adapterStateChangedIntentFilter = new IntentFilter();
+
+ adapterStateChangedIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ mContext.registerReceiverAsUser(mAdapterStateChangedReceiver, user,
+ adapterStateChangedIntentFilter, null, null);
+
+ IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
+
+ deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+ deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+ deviceStateChangedIntentFilter.addAction(
+ BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+ deviceStateChangedIntentFilter.addAction(
+ BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+ deviceStateChangedIntentFilter.addAction(
+ BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+
+ mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
+ deviceStateChangedIntentFilter, null, null);
+ }
+
+ @Override
+ public void stop() {
+ mContext.unregisterReceiver(mAdapterStateChangedReceiver);
+ mContext.unregisterReceiver(mDeviceStateChangedReceiver);
+ }
+
+ @Override
+ public boolean selectRoute(String deviceAddress) {
+ // Temporary no-op.
+ return false;
+ }
+
+ /**
+ * Transfers to a given bluetooth route.
+ * The dedicated BT device with the route would be activated.
+ *
+ * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of
+ * BT routes.
+ */
+ @Override
+ public void transferTo(@Nullable String routeId) {
+ if (routeId == null) {
+ clearActiveDevices();
+ return;
+ }
+
+ BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId);
+
+ if (btRouteInfo == null) {
+ Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId);
+ return;
+ }
+
+ if (mBluetoothAdapter != null) {
+ mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO);
+ }
+ }
+
+ private BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) {
+ if (routeId == null) {
+ return null;
+ }
+ for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
+ if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
+ return btRouteInfo;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Clears the active device for all known profiles.
+ */
+ private void clearActiveDevices() {
+ if (mBluetoothAdapter != null) {
+ mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO);
+ }
+ }
+
+ private void buildBluetoothRoutes() {
+ mBluetoothRoutes.clear();
+ Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
+ if (bondedDevices != null) {
+ for (BluetoothDevice device : bondedDevices) {
+ if (device.isConnected()) {
+ BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+ if (newBtRoute.mConnectedProfiles.size() > 0) {
+ mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+ }
+ }
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public MediaRoute2Info getSelectedRoute() {
+ // For now, active routes can be multiple only when a pair of hearing aid devices is active.
+ // Let the first active device represent them.
+ return (mActiveRoutes.isEmpty() ? null : mActiveRoutes.get(0).mRoute);
+ }
+
+ @NonNull
+ @Override
+ public List<MediaRoute2Info> getTransferableRoutes() {
+ List<MediaRoute2Info> routes = getAllBluetoothRoutes();
+ for (BluetoothRouteInfo btRoute : mActiveRoutes) {
+ routes.remove(btRoute.mRoute);
+ }
+ return routes;
+ }
+
+ @NonNull
+ @Override
+ public List<MediaRoute2Info> getAllBluetoothRoutes() {
+ List<MediaRoute2Info> routes = new ArrayList<>();
+ List<String> routeIds = new ArrayList<>();
+
+ MediaRoute2Info selectedRoute = getSelectedRoute();
+ if (selectedRoute != null) {
+ routes.add(selectedRoute);
+ routeIds.add(selectedRoute.getId());
+ }
+
+ for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+ // A pair of hearing aid devices or having the same hardware address
+ if (routeIds.contains(btRoute.mRoute.getId())) {
+ continue;
+ }
+ routes.add(btRoute.mRoute);
+ routeIds.add(btRoute.mRoute.getId());
+ }
+ return routes;
+ }
+
+ /**
+ * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}.
+ *
+ * @return true if devices can be handled by the provider.
+ */
+ @Override
+ public boolean updateVolumeForDevices(int devices, int volume) {
+ int routeType;
+ if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
+ routeType = MediaRoute2Info.TYPE_HEARING_AID;
+ } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
+ | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
+ | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
+ routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
+ routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
+ } else {
+ return false;
+ }
+ mVolumeMap.put(routeType, volume);
+
+ boolean shouldNotify = false;
+ for (BluetoothRouteInfo btRoute : mActiveRoutes) {
+ if (btRoute.mRoute.getType() != routeType) {
+ continue;
+ }
+ btRoute.mRoute = new MediaRoute2Info.Builder(btRoute.mRoute)
+ .setVolume(volume)
+ .build();
+ shouldNotify = true;
+ }
+ if (shouldNotify) {
+ notifyBluetoothRoutesUpdated();
+ }
+ return true;
+ }
+
+ private void notifyBluetoothRoutesUpdated() {
+ if (mListener != null) {
+ mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ }
+ }
+
+ private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+ BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+ newBtRoute.mBtDevice = device;
+
+ String routeId = device.getAddress();
+ String deviceName = device.getName();
+ if (TextUtils.isEmpty(deviceName)) {
+ deviceName = mContext.getResources().getText(R.string.unknownName).toString();
+ }
+ int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ newBtRoute.mConnectedProfiles = new SparseBooleanArray();
+ if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+ newBtRoute.mConnectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mHearingAidProfile != null
+ && mHearingAidProfile.getConnectedDevices().contains(device)) {
+ newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ // Intentionally assign the same ID for a pair of devices to publish only one of them.
+ routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device);
+ type = MediaRoute2Info.TYPE_HEARING_AID;
+ }
+ if (mLeAudioProfile != null
+ && mLeAudioProfile.getConnectedDevices().contains(device)) {
+ newBtRoute.mConnectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
+ routeId = LE_AUDIO_ROUTE_ID_PREFIX + mLeAudioProfile.getGroupId(device);
+ type = MediaRoute2Info.TYPE_BLE_HEADSET;
+ }
+
+ // Current volume will be set when connected.
+ newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+ .setDescription(mContext.getResources().getText(
+ R.string.bluetooth_a2dp_audio_route_name).toString())
+ .setType(type)
+ .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .setAddress(device.getAddress())
+ .build();
+ return newBtRoute;
+ }
+
+ private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
+ @MediaRoute2Info.ConnectionState int state) {
+ if (btRoute == null) {
+ Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
+ return;
+ }
+ if (btRoute.mRoute.getConnectionState() == state) {
+ return;
+ }
+
+ MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute)
+ .setConnectionState(state);
+ builder.setType(btRoute.getRouteType());
+
+ if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
+ builder.setVolume(mVolumeMap.get(btRoute.getRouteType(), 0));
+ }
+ btRoute.mRoute = builder.build();
+ }
+
+ private void addActiveRoute(BluetoothRouteInfo btRoute) {
+ if (btRoute == null) {
+ Slog.w(TAG, "addActiveRoute: btRoute is null");
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Adding active route: " + btRoute.mRoute);
+ }
+ if (mActiveRoutes.contains(btRoute)) {
+ Slog.w(TAG, "addActiveRoute: btRoute is already added.");
+ return;
+ }
+ setRouteConnectionState(btRoute, STATE_CONNECTED);
+ mActiveRoutes.add(btRoute);
+ }
+
+ private void removeActiveRoute(BluetoothRouteInfo btRoute) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing active route: " + btRoute.mRoute);
+ }
+ if (mActiveRoutes.remove(btRoute)) {
+ setRouteConnectionState(btRoute, STATE_DISCONNECTED);
+ }
+ }
+
+ private void clearActiveRoutesWithType(int type) {
+ if (DEBUG) {
+ Log.d(TAG, "Clearing active routes with type. type=" + type);
+ }
+ Iterator<BluetoothRouteInfo> iter = mActiveRoutes.iterator();
+ while (iter.hasNext()) {
+ BluetoothRouteInfo btRoute = iter.next();
+ if (btRoute.mRoute.getType() == type) {
+ iter.remove();
+ setRouteConnectionState(btRoute, STATE_DISCONNECTED);
+ }
+ }
+ }
+
+ private void addActiveDevices(BluetoothDevice device) {
+ // Let the given device be the first active device
+ BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress());
+ // This could happen if ACTION_ACTIVE_DEVICE_CHANGED is sent before
+ // ACTION_CONNECTION_STATE_CHANGED is sent.
+ if (activeBtRoute == null) {
+ activeBtRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), activeBtRoute);
+ }
+ addActiveRoute(activeBtRoute);
+
+ // A bluetooth route with the same route ID should be added.
+ for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+ if (TextUtils.equals(btRoute.mRoute.getId(), activeBtRoute.mRoute.getId())
+ && !TextUtils.equals(btRoute.mBtDevice.getAddress(),
+ activeBtRoute.mBtDevice.getAddress())) {
+ addActiveRoute(btRoute);
+ }
+ }
+ }
+
+ private static class BluetoothRouteInfo {
+ private BluetoothDevice mBtDevice;
+ private MediaRoute2Info mRoute;
+ private SparseBooleanArray mConnectedProfiles;
+
+ @MediaRoute2Info.Type
+ int getRouteType() {
+ // Let hearing aid profile have a priority.
+ if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ }
+
+ if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ }
+
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ }
+ }
+
+ // These callbacks run on the main thread.
+ private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ List<BluetoothDevice> activeDevices;
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = (BluetoothA2dp) proxy;
+ // It may contain null.
+ activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP);
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = (BluetoothHearingAid) proxy;
+ activeDevices = mBluetoothAdapter.getActiveDevices(
+ BluetoothProfile.HEARING_AID);
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudioProfile = (BluetoothLeAudio) proxy;
+ activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
+ break;
+ default:
+ return;
+ }
+ for (BluetoothDevice device : proxy.getConnectedDevices()) {
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ }
+ if (activeDevices.contains(device)) {
+ addActiveRoute(btRoute);
+ }
+ }
+ notifyBluetoothRoutesUpdated();
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = null;
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudioProfile = null;
+ break;
+ default:
+ return;
+ }
+ }
+ }
+
+ private class AdapterStateChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+ if (state == BluetoothAdapter.STATE_OFF
+ || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ mBluetoothRoutes.clear();
+ notifyBluetoothRoutesUpdated();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ buildBluetoothRoutes();
+ if (!mBluetoothRoutes.isEmpty()) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+
+ private class DeviceStateChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ BluetoothDevice device = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+
+ switch (intent.getAction()) {
+ case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+ if (device != null) {
+ addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
+ }
+ notifyBluetoothRoutesUpdated();
+ break;
+ case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID);
+ if (device != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Setting active hearing aid devices. device=" + device);
+ }
+
+ addActiveDevices(device);
+ }
+ notifyBluetoothRoutesUpdated();
+ break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET);
+ if (device != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Setting active le audio devices. device=" + device);
+ }
+
+ addActiveDevices(device);
+ }
+ notifyBluetoothRoutesUpdated();
+ break;
+ case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+ break;
+ case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device);
+ break;
+ case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.LE_AUDIO, intent, device);
+ break;
+ }
+ }
+
+ private void handleConnectionStateChanged(int profile, Intent intent,
+ BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ if (btRoute.mConnectedProfiles.size() > 0) {
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ notifyBluetoothRoutesUpdated();
+ }
+ } else {
+ btRoute.mConnectedProfiles.put(profile, true);
+ }
+ } else if (state == BluetoothProfile.STATE_DISCONNECTING
+ || state == BluetoothProfile.STATE_DISCONNECTED) {
+ if (btRoute != null) {
+ btRoute.mConnectedProfiles.delete(profile);
+ if (btRoute.mConnectedProfiles.size() == 0) {
+ removeActiveRoute(mBluetoothRoutes.remove(device.getAddress()));
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index 08691d0..d4a1184 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -68,6 +68,17 @@
*/
void stop();
+
+ /**
+ * Selects the route with the given {@code deviceAddress}.
+ *
+ * @param deviceAddress The physical address of the device to select. May be null to unselect
+ * the currently selected device.
+ * @return Whether the selection succeeds. If the selection fails, the state of the instance
+ * remains unaltered.
+ */
+ boolean selectRoute(@Nullable String deviceAddress);
+
/**
* Transfers Bluetooth output to the given route.
*
@@ -145,6 +156,12 @@
}
@Override
+ public boolean selectRoute(String deviceAddress) {
+ // no op
+ return false;
+ }
+
+ @Override
public void transferTo(String routeId) {
// no op
}
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
new file mode 100644
index 0000000..8bd6416
--- /dev/null
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
+import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.IAudioService;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Controls device routes.
+ *
+ * <p>A device route is a system wired route, for example, built-in speaker, wired
+ * headsets and headphones, dock, hdmi, or usb devices.
+ *
+ * <p>Thread safe.
+ *
+ * @see SystemMediaRoute2Provider
+ */
+/* package */ final class DeviceRouteController {
+
+ private static final String TAG = "WiredRoutesController";
+
+ private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final AudioManager mAudioManager;
+ @NonNull
+ private final IAudioService mAudioService;
+
+ @NonNull
+ private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ @NonNull
+ private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+
+ private int mDeviceVolume;
+ private MediaRoute2Info mDeviceRoute;
+
+ /* package */ static DeviceRouteController createInstance(@NonNull Context context,
+ @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
+ AudioManager audioManager = context.getSystemService(AudioManager.class);
+ IAudioService audioService = IAudioService.Stub.asInterface(
+ ServiceManager.getService(Context.AUDIO_SERVICE));
+
+ return new DeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
+ }
+
+ @VisibleForTesting
+ /* package */ DeviceRouteController(@NonNull Context context,
+ @NonNull AudioManager audioManager,
+ @NonNull IAudioService audioService,
+ @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(audioManager);
+ Objects.requireNonNull(audioService);
+ Objects.requireNonNull(onDeviceRouteChangedListener);
+
+ mContext = context;
+ mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
+
+ mAudioManager = audioManager;
+ mAudioService = audioService;
+
+ AudioRoutesInfo newAudioRoutes = null;
+ try {
+ newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
+ }
+
+ mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+ }
+
+ @NonNull
+ /* package */ synchronized MediaRoute2Info getDeviceRoute() {
+ return mDeviceRoute;
+ }
+
+ /* package */ synchronized boolean updateVolume(int volume) {
+ if (mDeviceVolume == volume) {
+ return false;
+ }
+
+ mDeviceVolume = volume;
+ mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
+ .setVolume(volume)
+ .build();
+
+ return true;
+ }
+
+ private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
+ int name = R.string.default_audio_route_name;
+ int type = TYPE_BUILTIN_SPEAKER;
+
+ if (newRoutes != null) {
+ if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+ type = TYPE_WIRED_HEADPHONES;
+ name = com.android.internal.R.string.default_audio_route_name_headphones;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ type = TYPE_WIRED_HEADSET;
+ name = com.android.internal.R.string.default_audio_route_name_headphones;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ type = TYPE_DOCK;
+ name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+ type = TYPE_HDMI;
+ name = com.android.internal.R.string.default_audio_route_name_external_device;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+ type = TYPE_USB_DEVICE;
+ name = com.android.internal.R.string.default_audio_route_name_usb;
+ }
+ }
+
+ synchronized (this) {
+ return new MediaRoute2Info.Builder(
+ DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
+ .setVolumeHandling(mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mDeviceVolume)
+ .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .setType(type)
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .addFeature(FEATURE_LIVE_VIDEO)
+ .addFeature(FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
+ .build();
+ }
+ }
+
+ private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ }
+
+ /* package */ interface OnDeviceRouteChangedListener {
+ void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+ }
+
+ private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
+
+ @Override
+ public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
+ MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+ synchronized (DeviceRouteController.this) {
+ mDeviceRoute = deviceRoute;
+ }
+ notifyDeviceRouteUpdate(deviceRoute);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 7979d2a..e31a7fc 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -52,7 +52,7 @@
import java.util.Set;
class LegacyBluetoothRouteController implements BluetoothRouteController {
- private static final String TAG = "BTRouteProvider";
+ private static final String TAG = "LBtRouteProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
@@ -132,6 +132,12 @@
mContext.unregisterReceiver(mDeviceStateChangedReceiver);
}
+ @Override
+ public boolean selectRoute(String deviceAddress) {
+ // No-op as the class decides if a route is selected based on Bluetooth events.
+ return false;
+ }
+
/**
* Transfers to a given bluetooth route.
* The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 76ff19f..638e81a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,25 +16,12 @@
package com.android.server.media;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
-import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
-import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_DOCK;
-import static android.media.MediaRoute2Info.TYPE_HDMI;
-import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.IAudioService;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
@@ -43,14 +30,11 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import java.util.Objects;
@@ -68,23 +52,21 @@
SystemMediaRoute2Provider.class.getName());
static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
- static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
private final AudioManager mAudioManager;
- private final IAudioService mAudioService;
private final Handler mHandler;
private final Context mContext;
private final UserHandle mUser;
+
+ private final DeviceRouteController mDeviceRouteController;
private final BluetoothRouteController mBtRouteProvider;
private String mSelectedRouteId;
// For apps without MODIFYING_AUDIO_ROUTING permission.
// This should be the currently selected route.
MediaRoute2Info mDefaultRoute;
- MediaRoute2Info mDeviceRoute;
RoutingSessionInfo mDefaultSessionInfo;
- int mDeviceVolume;
private final AudioManagerBroadcastReceiver mAudioReceiver =
new AudioManagerBroadcastReceiver();
@@ -93,19 +75,6 @@
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
- final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
- @Override
- public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
- mHandler.post(() -> {
- updateDeviceRoute(newRoutes);
- notifyProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
- }
- };
-
SystemMediaRoute2Provider(Context context, UserHandle user) {
super(COMPONENT_NAME);
mIsSystemRouteProvider = true;
@@ -114,8 +83,6 @@
mHandler = new Handler(Looper.getMainLooper());
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mAudioService = IAudioService.Stub.asInterface(
- ServiceManager.getService(Context.AUDIO_SERVICE));
mBtRouteProvider = BluetoothRouteController.createInstance(context, (routes) -> {
publishProviderState();
@@ -124,15 +91,18 @@
}
});
- AudioRoutesInfo newAudioRoutes = null;
- try {
- newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
- } catch (RemoteException e) {
- }
+ mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
+ mHandler.post(() -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ });
- // The methods below should be called after all fields are initialized, as they
+ // These methods below should be called after all fields are initialized, as they
// access the fields inside.
- updateDeviceRoute(newAudioRoutes);
+ updateProviderState();
updateSessionInfosIfNeeded();
}
@@ -216,7 +186,9 @@
// The currently selected route is the default route.
return;
}
- if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
+
+ MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+ if (TextUtils.equals(routeId, deviceRoute.getId())) {
mBtRouteProvider.transferTo(null);
} else {
mBtRouteProvider.transferTo(routeId);
@@ -254,9 +226,12 @@
if (mSessionInfos.isEmpty()) {
return null;
}
+
+ MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, packageName).setSystemSession(true);
- builder.addSelectedRoute(mDeviceRoute.getId());
+ builder.addSelectedRoute(deviceRoute.getId());
for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
builder.addTransferableRoute(route.getId());
}
@@ -264,47 +239,12 @@
}
}
- private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
- int name = R.string.default_audio_route_name;
- int type = TYPE_BUILTIN_SPEAKER;
- if (newRoutes != null) {
- if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
- type = TYPE_WIRED_HEADPHONES;
- name = com.android.internal.R.string.default_audio_route_name_headphones;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
- type = TYPE_WIRED_HEADSET;
- name = com.android.internal.R.string.default_audio_route_name_headphones;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
- type = TYPE_DOCK;
- name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
- type = TYPE_HDMI;
- name = com.android.internal.R.string.default_audio_route_name_external_device;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
- type = TYPE_USB_DEVICE;
- name = com.android.internal.R.string.default_audio_route_name_usb;
- }
- }
-
- mDeviceRoute = new MediaRoute2Info.Builder(
- DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
- .setVolumeHandling(mAudioManager.isVolumeFixed()
- ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
- : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .setVolume(mDeviceVolume)
- .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setType(type)
- .addFeature(FEATURE_LIVE_AUDIO)
- .addFeature(FEATURE_LIVE_VIDEO)
- .addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
- updateProviderState();
- }
-
private void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
- builder.addRoute(mDeviceRoute);
+
+ // We must have a device route in the provider info.
+ builder.addRoute(mDeviceRouteController.getDeviceRoute());
+
for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
builder.addRoute(route);
}
@@ -327,11 +267,12 @@
SYSTEM_SESSION_ID, "" /* clientPackageName */)
.setSystemSession(true);
- MediaRoute2Info selectedRoute = mDeviceRoute;
+ MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+ MediaRoute2Info selectedRoute = deviceRoute;
MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
if (selectedBtRoute != null) {
selectedRoute = selectedBtRoute;
- builder.addTransferableRoute(mDeviceRoute.getId());
+ builder.addTransferableRoute(deviceRoute.getId());
}
mSelectedRouteId = selectedRoute.getId();
mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
@@ -423,12 +364,9 @@
if (mBtRouteProvider.updateVolumeForDevices(devices, volume)) {
return;
}
- if (mDeviceVolume != volume) {
- mDeviceVolume = volume;
- mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
- .setVolume(volume)
- .build();
- }
+
+ mDeviceRouteController.updateVolume(volume);
+
publishProviderState();
}
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index 41c2fbf..6c32ec2 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -17,6 +17,8 @@
package com.android.server.sensors;
import android.annotation.NonNull;
+import android.hardware.SensorDirectChannel;
+import android.os.ParcelFileDescriptor;
import java.util.concurrent.Executor;
@@ -58,7 +60,7 @@
* @return The sensor handle.
*/
public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
- @NonNull String vendor, @NonNull RuntimeSensorCallback callback);
+ @NonNull String vendor, int flags, @NonNull RuntimeSensorCallback callback);
/**
* Unregisters the sensor with the given handle from the framework.
@@ -98,9 +100,31 @@
public interface RuntimeSensorCallback {
/**
* Invoked when the listeners of the runtime sensor have changed.
- * Returns an error code if the invocation was unsuccessful, zero otherwise.
+ * Returns zero on success, negative error code otherwise.
*/
int onConfigurationChanged(int handle, boolean enabled, int samplingPeriodMicros,
int batchReportLatencyMicros);
+
+ /**
+ * Invoked when a direct sensor channel has been created.
+ * Wraps the file descriptor in a {@link android.os.SharedMemory} object and passes it to
+ * the client process.
+ * Returns a positive identifier of the channel on success, negative error code otherwise.
+ */
+ int onDirectChannelCreated(ParcelFileDescriptor fd);
+
+ /**
+ * Invoked when a direct sensor channel has been destroyed.
+ */
+ void onDirectChannelDestroyed(int channelHandle);
+
+ /**
+ * Invoked when a direct sensor channel has been configured for a sensor.
+ * If the invocation is unsuccessful, a negative error code is returned.
+ * On success, the return value is zero if the rate level is {@code RATE_STOP}, and a
+ * positive report token otherwise.
+ */
+ int onDirectChannelConfigured(int channelHandle, int sensorHandle,
+ @SensorDirectChannel.RateLevel int rateLevel);
}
}
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 9790659..1baa0a6 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -56,7 +56,8 @@
private static native void unregisterProximityActiveListenerNative(long ptr);
private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
- String name, String vendor, SensorManagerInternal.RuntimeSensorCallback callback);
+ String name, String vendor, int flags,
+ SensorManagerInternal.RuntimeSensorCallback callback);
private static native void unregisterRuntimeSensorNative(long ptr, int handle);
private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
long timestampNanos, float[] values);
@@ -95,9 +96,9 @@
class LocalService extends SensorManagerInternal {
@Override
public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
- @NonNull String vendor, @NonNull RuntimeSensorCallback callback) {
+ @NonNull String vendor, int flags, @NonNull RuntimeSensorCallback callback) {
synchronized (mLock) {
- int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+ int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor, flags,
callback);
mRuntimeSensorHandles.add(handle);
return handle;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e2bdcdd..2ce86ad 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -179,6 +179,7 @@
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
+ "android.hardware.thermal-V1-ndk",
"android.hardware.tv.input@1.0",
"android.hardware.tv.input-V1-ndk",
"android.hardware.vibrator-V2-cpp",
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index ed79352..7e0bb68 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -16,27 +16,28 @@
#define LOG_TAG "HardwarePropertiesManagerService-JNI"
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
-
-#include <math.h>
-#include <stdlib.h>
-
+#include <aidl/android/hardware/thermal/IThermal.h>
+#include <android/binder_manager.h>
#include <android/hardware/thermal/1.0/IThermal.h>
+#include <math.h>
+#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include "core_jni_helpers.h"
+#include "jni.h"
namespace android {
+using ::aidl::android::hardware::thermal::CoolingDevice;
+using ::aidl::android::hardware::thermal::IThermal;
+using ::aidl::android::hardware::thermal::Temperature;
+using ::aidl::android::hardware::thermal::TemperatureThreshold;
+using ::aidl::android::hardware::thermal::TemperatureType;
+using ::aidl::android::hardware::thermal::ThrottlingSeverity;
using android::hidl::base::V1_0::IBase;
using hardware::hidl_death_recipient;
using hardware::hidl_vec;
-using hardware::thermal::V1_0::CoolingDevice;
-using hardware::thermal::V1_0::CpuUsage;
-using hardware::thermal::V1_0::IThermal;
-using hardware::thermal::V1_0::Temperature;
using hardware::thermal::V1_0::ThermalStatus;
using hardware::thermal::V1_0::ThermalStatusCode;
template<typename T>
@@ -62,20 +63,28 @@
static void getThermalHalLocked();
static std::mutex gThermalHalMutex;
-static sp<IThermal> gThermalHal = nullptr;
+static sp<hardware::thermal::V1_0::IThermal> gThermalHidlHal = nullptr;
+static std::shared_ptr<IThermal> gThermalAidlHal = nullptr;
-// struct ThermalHalDeathRecipient;
-struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
- // hidl_death_recipient interface
- virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
- std::lock_guard<std::mutex> lock(gThermalHalMutex);
- ALOGE("ThermalHAL just died");
- gThermalHal = nullptr;
- getThermalHalLocked();
- }
+struct ThermalHidlHalDeathRecipient : virtual public hidl_death_recipient {
+ // hidl_death_recipient interface
+ virtual void serviceDied(uint64_t cookie, const wp<IBase> &who) override {
+ std::lock_guard<std::mutex> lock(gThermalHalMutex);
+ ALOGE("Thermal HAL just died");
+ gThermalHidlHal = nullptr;
+ getThermalHalLocked();
+ }
};
-sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
+static void onThermalAidlBinderDied(void *cookie) {
+ std::lock_guard<std::mutex> lock(gThermalHalMutex);
+ ALOGE("Thermal AIDL HAL just died");
+ gThermalAidlHal = nullptr;
+ getThermalHalLocked();
+}
+
+sp<ThermalHidlHalDeathRecipient> gThermalHidlHalDeathRecipient = nullptr;
+ndk::ScopedAIBinder_DeathRecipient gThermalAidlDeathRecipient;
// ----------------------------------------------------------------------------
@@ -85,27 +94,49 @@
// The caller must be holding gThermalHalMutex.
static void getThermalHalLocked() {
- if (gThermalHal != nullptr) {
+ if (gThermalAidlHal || gThermalHidlHal) {
+ return;
+ }
+ const std::string thermalInstanceName = std::string(IThermal::descriptor) + "/default";
+ if (AServiceManager_isDeclared(thermalInstanceName.c_str())) {
+ auto binder = AServiceManager_waitForService(thermalInstanceName.c_str());
+ auto thermalAidlService = IThermal::fromBinder(ndk::SpAIBinder(binder));
+ if (thermalAidlService) {
+ gThermalAidlHal = thermalAidlService;
+ if (gThermalAidlDeathRecipient.get() == nullptr) {
+ gThermalAidlDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(
+ AIBinder_DeathRecipient_new(onThermalAidlBinderDied));
+ }
+ auto linked = AIBinder_linkToDeath(thermalAidlService->asBinder().get(),
+ gThermalAidlDeathRecipient.get(), nullptr);
+ if (linked != STATUS_OK) {
+ ALOGW("Failed to link to death (AIDL): %d", linked);
+ gThermalAidlHal = nullptr;
+ }
+ } else {
+ ALOGE("Unable to get Thermal AIDL service");
+ }
return;
}
- gThermalHal = IThermal::getService();
+ ALOGI("Thermal AIDL service is not declared, trying HIDL");
+ gThermalHidlHal = hardware::thermal::V1_0::IThermal::getService();
- if (gThermalHal == nullptr) {
+ if (gThermalHidlHal == nullptr) {
ALOGE("Unable to get Thermal service.");
} else {
- if (gThermalHalDeathRecipient == nullptr) {
- gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
+ if (gThermalHidlHalDeathRecipient == nullptr) {
+ gThermalHidlHalDeathRecipient = new ThermalHidlHalDeathRecipient();
}
- hardware::Return<bool> linked = gThermalHal->linkToDeath(
- gThermalHalDeathRecipient, 0x451F /* cookie */);
+ hardware::Return<bool> linked =
+ gThermalHidlHal->linkToDeath(gThermalHidlHalDeathRecipient, 0x451F /* cookie */);
if (!linked.isOk()) {
ALOGE("Transaction error in linking to ThermalHAL death: %s",
- linked.description().c_str());
- gThermalHal = nullptr;
+ linked.description().c_str());
+ gThermalHidlHal = nullptr;
} else if (!linked) {
ALOGW("Unable to link to ThermalHal death notifications");
- gThermalHal = nullptr;
+ gThermalHidlHal = nullptr;
} else {
ALOGD("Link to death notification successful");
}
@@ -117,17 +148,27 @@
getThermalHalLocked();
}
-static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) {
- std::lock_guard<std::mutex> lock(gThermalHalMutex);
- getThermalHalLocked();
- if (gThermalHal == nullptr) {
- ALOGE("Couldn't get fan speeds because of HAL error.");
+static jfloatArray getFanSpeedsAidl(JNIEnv *env) {
+ std::vector<CoolingDevice> list;
+ auto status = gThermalAidlHal->getCoolingDevices(&list);
+ if (!status.isOk()) {
+ ALOGE("getFanSpeeds failed status: %s", status.getMessage());
return env->NewFloatArray(0);
}
+ float values[list.size()];
+ for (size_t i = 0; i < list.size(); ++i) {
+ values[i] = list[i].value;
+ }
+ jfloatArray fanSpeeds = env->NewFloatArray(list.size());
+ env->SetFloatArrayRegion(fanSpeeds, 0, list.size(), values);
+ return fanSpeeds;
+}
- hidl_vec<CoolingDevice> list;
- Return<void> ret = gThermalHal->getCoolingDevices(
- [&list](ThermalStatus status, hidl_vec<CoolingDevice> devices) {
+static jfloatArray getFanSpeedsHidl(JNIEnv *env) {
+ hidl_vec<hardware::thermal::V1_0::CoolingDevice> list;
+ Return<void> ret = gThermalHidlHal->getCoolingDevices(
+ [&list](ThermalStatus status,
+ hidl_vec<hardware::thermal::V1_0::CoolingDevice> devices) {
if (status.code == ThermalStatusCode::SUCCESS) {
list = std::move(devices);
} else {
@@ -137,9 +178,9 @@
});
if (!ret.isOk()) {
- ALOGE("getCoolingDevices failed status: %s", ret.description().c_str());
+ ALOGE("getFanSpeeds failed status: %s", ret.description().c_str());
+ return env->NewFloatArray(0);
}
-
float values[list.size()];
for (size_t i = 0; i < list.size(); ++i) {
values[i] = list[i].currentValue;
@@ -149,17 +190,79 @@
return fanSpeeds;
}
-static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type,
- int source) {
+static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) {
std::lock_guard<std::mutex> lock(gThermalHalMutex);
getThermalHalLocked();
- if (gThermalHal == nullptr) {
- ALOGE("Couldn't get device temperatures because of HAL error.");
+ if (!gThermalHidlHal && !gThermalAidlHal) {
+ ALOGE("Couldn't get fan speeds because of HAL error.");
return env->NewFloatArray(0);
}
- hidl_vec<Temperature> list;
- Return<void> ret = gThermalHal->getTemperatures(
- [&list](ThermalStatus status, hidl_vec<Temperature> temperatures) {
+ if (gThermalAidlHal) {
+ return getFanSpeedsAidl(env);
+ }
+ return getFanSpeedsHidl(env);
+}
+
+static jfloatArray getDeviceTemperaturesAidl(JNIEnv *env, int type, int source) {
+ jfloat *values;
+ size_t length = 0;
+ if (source == TEMPERATURE_CURRENT) {
+ std::vector<Temperature> list;
+ auto status =
+ gThermalAidlHal->getTemperaturesWithType(static_cast<TemperatureType>(type), &list);
+
+ if (!status.isOk()) {
+ ALOGE("getDeviceTemperatures failed status: %s", status.getMessage());
+ return env->NewFloatArray(0);
+ }
+ values = new jfloat[list.size()];
+ for (const auto &temp : list) {
+ if (static_cast<int>(temp.type) == type) {
+ values[length++] = finalizeTemperature(temp.value);
+ }
+ }
+ } else if (source == TEMPERATURE_THROTTLING_BELOW_VR_MIN) {
+ values = new jfloat[1];
+ values[length++] = gUndefinedTemperature;
+ } else {
+ std::vector<TemperatureThreshold> list;
+ auto status =
+ gThermalAidlHal->getTemperatureThresholdsWithType(static_cast<TemperatureType>(
+ type),
+ &list);
+
+ if (!status.isOk()) {
+ ALOGE("getDeviceTemperatures failed status: %s", status.getMessage());
+ return env->NewFloatArray(0);
+ }
+ values = new jfloat[list.size()];
+ for (auto &t : list) {
+ if (static_cast<int>(t.type) == type) {
+ switch (source) {
+ case TEMPERATURE_THROTTLING:
+ values[length++] =
+ finalizeTemperature(t.hotThrottlingThresholds[static_cast<int>(
+ ThrottlingSeverity::SEVERE)]);
+ break;
+ case TEMPERATURE_SHUTDOWN:
+ values[length++] =
+ finalizeTemperature(t.hotThrottlingThresholds[static_cast<int>(
+ ThrottlingSeverity::SHUTDOWN)]);
+ break;
+ }
+ }
+ }
+ }
+ jfloatArray deviceTemps = env->NewFloatArray(length);
+ env->SetFloatArrayRegion(deviceTemps, 0, length, values);
+ return deviceTemps;
+}
+
+static jfloatArray getDeviceTemperaturesHidl(JNIEnv *env, int type, int source) {
+ hidl_vec<hardware::thermal::V1_0::Temperature> list;
+ Return<void> ret = gThermalHidlHal->getTemperatures(
+ [&list](ThermalStatus status,
+ hidl_vec<hardware::thermal::V1_0::Temperature> temperatures) {
if (status.code == ThermalStatusCode::SUCCESS) {
list = std::move(temperatures);
} else {
@@ -170,9 +273,9 @@
if (!ret.isOk()) {
ALOGE("getDeviceTemperatures failed status: %s", ret.description().c_str());
+ return env->NewFloatArray(0);
}
-
- jfloat values[list.size()];
+ float values[list.size()];
size_t length = 0;
for (size_t i = 0; i < list.size(); ++i) {
if (static_cast<int>(list[i].type) == type) {
@@ -197,16 +300,34 @@
return deviceTemps;
}
+static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type,
+ int source) {
+ std::lock_guard<std::mutex> lock(gThermalHalMutex);
+ getThermalHalLocked();
+ if (!gThermalHidlHal && !gThermalAidlHal) {
+ ALOGE("Couldn't get device temperatures because of HAL error.");
+ return env->NewFloatArray(0);
+ }
+ if (gThermalAidlHal) {
+ return getDeviceTemperaturesAidl(env, type, source);
+ }
+ return getDeviceTemperaturesHidl(env, type, source);
+}
+
static jobjectArray nativeGetCpuUsages(JNIEnv *env, jclass /* clazz */) {
std::lock_guard<std::mutex> lock(gThermalHalMutex);
getThermalHalLocked();
- if (gThermalHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) {
+ if (gThermalAidlHal) {
+ ALOGW("getCpuUsages is not supported");
+ return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
+ }
+ if (gThermalHidlHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) {
ALOGE("Couldn't get CPU usages because of HAL error.");
return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
}
- hidl_vec<CpuUsage> list;
- Return<void> ret = gThermalHal->getCpuUsages(
- [&list](ThermalStatus status, hidl_vec<CpuUsage> cpuUsages) {
+ hidl_vec<hardware::thermal::V1_0::CpuUsage> list;
+ Return<void> ret = gThermalHidlHal->getCpuUsages(
+ [&list](ThermalStatus status, hidl_vec<hardware::thermal::V1_0::CpuUsage> cpuUsages) {
if (status.code == ThermalStatusCode::SUCCESS) {
list = std::move(cpuUsages);
} else {
@@ -217,6 +338,7 @@
if (!ret.isOk()) {
ALOGE("getCpuUsages failed status: %s", ret.description().c_str());
+ return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
}
jobjectArray cpuUsages = env->NewObjectArray(list.size(), gCpuUsageInfoClassInfo.clazz,
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 356e9a9..a916b64 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -17,10 +17,13 @@
#define LOG_TAG "NativeSensorService"
#include <android-base/properties.h>
+#include <android_os_NativeHandle.h>
#include <android_runtime/AndroidRuntime.h>
#include <core_jni_helpers.h>
+#include <cutils/native_handle.h>
#include <cutils/properties.h>
#include <jni.h>
+#include <nativehelper/JNIPlatformHelp.h>
#include <sensorservice/SensorService.h>
#include <string.h>
#include <utils/Log.h>
@@ -28,6 +31,8 @@
#include <mutex>
+#include "android_util_Binder.h"
+
#define PROXIMITY_ACTIVE_CLASS \
"com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
@@ -38,7 +43,10 @@
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnProximityActive;
-static jmethodID sMethodIdOnConfigurationChanged;
+static jmethodID sMethodIdRuntimeSensorOnConfigurationChanged;
+static jmethodID sMethodIdRuntimeSensorOnDirectChannelCreated;
+static jmethodID sMethodIdRuntimeSensorOnDirectChannelDestroyed;
+static jmethodID sMethodIdRuntimeSensorOnDirectChannelConfigured;
class NativeSensorService {
public:
@@ -47,7 +55,7 @@
void registerProximityActiveListener();
void unregisterProximityActiveListener();
jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
- jobject callback);
+ jint flags, jobject callback);
void unregisterRuntimeSensor(jint handle);
jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
jfloatArray values);
@@ -74,6 +82,9 @@
status_t onConfigurationChanged(int32_t handle, bool enabled, int64_t samplingPeriodNs,
int64_t batchReportLatencyNs) override;
+ int onDirectChannelCreated(int fd) override;
+ void onDirectChannelDestroyed(int channelHandle) override;
+ int onDirectChannelConfigured(int channelHandle, int sensorHandle, int rateLevel) override;
private:
jobject mCallback;
@@ -108,7 +119,7 @@
}
jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
- jstring vendor, jobject callback) {
+ jstring vendor, jint flags, jobject callback) {
if (mService == nullptr) {
ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
return -1;
@@ -119,6 +130,11 @@
.vendor = env->GetStringUTFChars(vendor, 0),
.version = sizeof(sensor_t),
.type = type,
+#ifdef __LP64__
+ .flags = static_cast<uint64_t>(flags),
+#else
+ .flags = static_cast<uint32_t>(flags),
+#endif
};
sp<RuntimeSensorCallbackDelegate> callbackDelegate(
@@ -234,12 +250,39 @@
status_t NativeSensorService::RuntimeSensorCallbackDelegate::onConfigurationChanged(
int32_t handle, bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
- return jniEnv->CallIntMethod(mCallback, sMethodIdOnConfigurationChanged,
+ return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnConfigurationChanged,
static_cast<jint>(handle), static_cast<jboolean>(enabled),
static_cast<jint>(ns2us(samplingPeriodNs)),
static_cast<jint>(ns2us(batchReportLatencyNs)));
}
+int NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelCreated(int fd) {
+ if (fd <= 0) {
+ return 0;
+ }
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jobject jfd = jniCreateFileDescriptor(jniEnv, fd);
+ jobject parcelFileDescriptor = newParcelFileDescriptor(jniEnv, jfd);
+ return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelCreated,
+ parcelFileDescriptor);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelDestroyed(
+ int channelHandle) {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ return jniEnv->CallVoidMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelDestroyed,
+ static_cast<jint>(channelHandle));
+}
+
+int NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelConfigured(int channelHandle,
+ int sensorHandle,
+ int rateLevel) {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelConfigured,
+ static_cast<jint>(channelHandle), static_cast<jint>(sensorHandle),
+ static_cast<jint>(rateLevel));
+}
+
static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
NativeSensorService* service = new NativeSensorService(env, listener);
return reinterpret_cast<jlong>(service);
@@ -256,9 +299,10 @@
}
static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
- jstring name, jstring vendor, jobject callback) {
+ jstring name, jstring vendor, jint flags,
+ jobject callback) {
auto* service = reinterpret_cast<NativeSensorService*>(ptr);
- return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+ return service->registerRuntimeSensor(env, deviceId, type, name, vendor, flags, callback);
}
static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
@@ -280,7 +324,7 @@
{"unregisterProximityActiveListenerNative", "(J)V",
reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
{"registerRuntimeSensorNative",
- "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+ "(JIILjava/lang/String;Ljava/lang/String;IL" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
reinterpret_cast<void*>(registerRuntimeSensorNative)},
{"unregisterRuntimeSensorNative", "(JI)V",
reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
@@ -293,8 +337,17 @@
jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
- sMethodIdOnConfigurationChanged =
+ sMethodIdRuntimeSensorOnConfigurationChanged =
GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onConfigurationChanged", "(IZII)I");
+ sMethodIdRuntimeSensorOnDirectChannelCreated =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelCreated",
+ "(Landroid/os/ParcelFileDescriptor;)I");
+ sMethodIdRuntimeSensorOnDirectChannelDestroyed =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelDestroyed", "(I)V");
+ sMethodIdRuntimeSensorOnDirectChannelConfigured =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelConfigured",
+ "(III)I");
+
return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
NELEM(methods));
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8c94b0a..6498b6a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -415,25 +415,8 @@
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
- // Action entry is expected to either contain the final GetCredentialResponse, or it is
- // also acceptable if it does not contain anything. In the second case, we re-show this
- // action on the UI.
- if (providerPendingIntentResponse == null) {
- Log.i(TAG, "providerPendingIntentResponse is null");
- return;
- }
-
- GetCredentialException exception = maybeGetPendingIntentException(
- providerPendingIntentResponse);
- if (exception != null) {
- invokeCallbackWithError(exception.getType(), exception.getMessage());
- }
- GetCredentialResponse response = PendingIntentResultHandler
- .extractGetCredentialResponse(
- providerPendingIntentResponse.getResultData());
- if (response != null) {
- mCallbacks.onFinalResponseReceived(mComponentName, response);
- }
+ Log.i(TAG, "onActionEntrySelected");
+ onCredentialEntrySelected(providerPendingIntentResponse);
}
@@ -450,11 +433,11 @@
/**
* When an invalid state occurs, e.g. entry mismatch or no response from provider,
- * we send back a TYPE_UNKNOWN error as to the developer.
+ * we send back a TYPE_NO_CREDENTIAL error as to the developer.
*/
private void invokeCallbackOnInternalInvalidState() {
mCallbacks.onFinalErrorReceived(mComponentName,
- GetCredentialException.TYPE_UNKNOWN, null);
+ GetCredentialException.TYPE_NO_CREDENTIAL, null);
}
/** Update auth entries status based on an auth entry selected from a different session. */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 95c2ed2..99da415 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -759,40 +759,6 @@
}
/**
- * Verify that sending a broadcast that removes any matching pending
- * broadcasts is applied as expected.
- */
- @Test
- public void testRemoveMatchingFilter() {
- final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
- final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
- optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
-
- final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
- final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
- optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
-
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
- mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
- mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
- mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
- mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
-
- // While we're here, give our health check some test coverage
- mImpl.checkHealthLocked();
-
- // Marching through the queue we should only have one SCREEN_OFF
- // broadcast, since that's the last state we dispatched
- final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
- getUidForPackage(PACKAGE_GREEN));
- queue.makeActiveNextPending();
- assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
- assertTrue(queue.isEmpty());
- }
-
- /**
* Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 95a5884..5f82ec1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -53,6 +53,7 @@
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index 6431e88..1259d71 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -81,7 +81,7 @@
@Test
public void createSensor_invalidHandle_throwsException() {
doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
- anyInt(), anyInt(), anyString(), anyString(), any());
+ anyInt(), anyInt(), anyString(), anyString(), anyInt(), any());
Throwable thrown = assertThrows(
RuntimeException.class,
@@ -138,7 +138,7 @@
private void doCreateSensorSuccessfully() {
doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
- anyInt(), anyInt(), anyString(), anyString(), any());
+ anyInt(), anyInt(), anyString(), anyString(), anyInt(), any());
assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig))
.isEqualTo(SENSOR_HANDLE);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9910a80..8f4c81f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -500,7 +500,7 @@
.build();
doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
- anyInt(), anyInt(), anyString(), anyString(), any());
+ anyInt(), anyInt(), anyString(), anyString(), anyInt(), any());
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
VirtualSensor sensor = mLocalService.getVirtualSensor(VIRTUAL_DEVICE_ID_1, SENSOR_HANDLE);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 800f60b..ffe2fec 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -43,6 +43,7 @@
import com.android.server.display.BrightnessThrottler.Injector;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.mode.DisplayModeDirectorTest;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 6def7b1..8981160 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -176,21 +176,18 @@
assertFalse(mInjector.mColorSamplingEnabled);
// Update brightness config to enabled color sampling.
- mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
- /* collectColorSamples= */ true));
+ mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
mInjector.waitForHandler();
assertTrue(mInjector.mColorSamplingEnabled);
// Update brightness config to disable color sampling.
- mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
- /* collectColorSamples= */ false));
+ mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false);
mInjector.waitForHandler();
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is off, update config to turn on color sampling.
mInjector.sendScreenChange(/* screenOn= */ false);
- mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
- /* collectColorSamples= */ true));
+ mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
mInjector.waitForHandler();
assertFalse(mInjector.mColorSamplingEnabled);
@@ -883,7 +880,7 @@
private void startTracker(BrightnessTracker tracker, float initialBrightness,
boolean collectColorSamples) {
tracker.start(initialBrightness);
- tracker.setBrightnessConfiguration(buildBrightnessConfiguration(collectColorSamples));
+ tracker.setShouldCollectColorSample(collectColorSamples);
mInjector.waitForHandler();
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
rename to services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 7e6eeee..f256c8a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.display;
+package com.android.server.display.mode;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
@@ -27,8 +27,7 @@
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
-import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE;
-import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE;
import static com.google.common.truth.Truth.assertThat;
@@ -88,9 +87,11 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
-import com.android.server.display.DisplayModeDirector.BrightnessObserver;
-import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
-import com.android.server.display.DisplayModeDirector.Vote;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.TestUtils;
+import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
+import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
+import com.android.server.display.mode.DisplayModeDirector.Vote;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -126,6 +127,8 @@
private static final int DISPLAY_ID = 0;
private static final float TRANSITION_POINT = 0.763f;
+ private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
private Context mContext;
private FakesInjector mInjector;
private Handler mHandler;
@@ -2234,7 +2237,7 @@
ArgumentCaptor.forClass(IThermalEventListener.class);
verify(mThermalServiceMock).registerThermalEventListenerWithType(
- thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
+ thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
final IThermalEventListener listener = thermalEventListener.getValue();
// Verify that there is no skin temperature vote initially.
@@ -2548,7 +2551,7 @@
KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
}
- void setBrightnessThrottlingData(String brightnessThrottlingData) {
+ public void setBrightnessThrottlingData(String brightnessThrottlingData) {
putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
new file mode 100644
index 0000000..24ed42c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.annotation.IdRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.server.audio.AudioService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Enclosed.class)
+public class DeviceRouteControllerTest {
+
+ private static final String DEFAULT_ROUTE_NAME = "default_route";
+ private static final String DEFAULT_HEADPHONES_NAME = "headphone";
+ private static final String DEFAULT_HEADSET_NAME = "headset";
+ private static final String DEFAULT_DOCK_NAME = "dock";
+ private static final String DEFAULT_HDMI_NAME = "hdmi";
+ private static final String DEFAULT_USB_NAME = "usb";
+ private static final int VOLUME_DEFAULT_VALUE = 0;
+ private static final int VOLUME_VALUE_SAMPLE_1 = 10;
+
+ private static AudioRoutesInfo createFakeBluetoothAudioRoute() {
+ AudioRoutesInfo btRouteInfo = new AudioRoutesInfo();
+ btRouteInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
+ btRouteInfo.bluetoothName = "bt_device";
+ return btRouteInfo;
+ }
+
+ @RunWith(JUnit4.class)
+ public static class DefaultDeviceRouteValueTest {
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private AudioService mAudioService;
+ @Mock
+ private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void initialize_noRoutesInfo_defaultRouteIsNotNull() {
+ // Mocking default_audio_route_name.
+ when(mResources.getText(R.string.default_audio_route_name))
+ .thenReturn(DEFAULT_ROUTE_NAME);
+
+ // Default route should be initialized even when AudioService returns null.
+ when(mAudioService.startWatchingRoutes(any())).thenReturn(null);
+
+ DeviceRouteController deviceRouteController = new DeviceRouteController(
+ mContext,
+ mAudioManager,
+ mAudioService,
+ mOnDeviceRouteChangedListener
+ );
+
+ MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+
+ assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
+ .isTrue();
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+ }
+
+ @Test
+ public void initialize_bluetoothRouteAvailable_deviceRouteReturnsDefaultRoute() {
+ // Mocking default_audio_route_name.
+ when(mResources.getText(R.string.default_audio_route_name))
+ .thenReturn(DEFAULT_ROUTE_NAME);
+
+ // This route should be ignored.
+ AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
+ when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute);
+
+ DeviceRouteController deviceRouteController = new DeviceRouteController(
+ mContext,
+ mAudioManager,
+ mAudioService,
+ mOnDeviceRouteChangedListener
+ );
+
+ MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+
+ assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
+ .isTrue();
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+ }
+ }
+
+ @RunWith(Parameterized.class)
+ public static class DeviceRouteInitializationTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { /* expected res */
+ com.android.internal.R.string.default_audio_route_name_headphones,
+ /* expected name */
+ DEFAULT_HEADPHONES_NAME,
+ /* expected type */
+ MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+ /* actual audio route type */
+ AudioRoutesInfo.MAIN_HEADPHONES },
+ { /* expected res */
+ com.android.internal.R.string.default_audio_route_name_headphones,
+ /* expected name */
+ DEFAULT_HEADSET_NAME,
+ /* expected type */
+ MediaRoute2Info.TYPE_WIRED_HEADSET,
+ /* actual audio route type */
+ AudioRoutesInfo.MAIN_HEADSET },
+ { /* expected res */
+ R.string.default_audio_route_name_dock_speakers,
+ /* expected name */
+ DEFAULT_DOCK_NAME,
+ /* expected type */
+ MediaRoute2Info.TYPE_DOCK,
+ /* actual audio route type */
+ AudioRoutesInfo.MAIN_DOCK_SPEAKERS },
+ { /* expected res */
+ R.string.default_audio_route_name_external_device,
+ /* expected name */
+ DEFAULT_HDMI_NAME,
+ /* expected type */
+ MediaRoute2Info.TYPE_HDMI,
+ /* actual audio route type */
+ AudioRoutesInfo.MAIN_HDMI },
+ { /* expected res */
+ R.string.default_audio_route_name_usb,
+ /* expected name */
+ DEFAULT_USB_NAME,
+ /* expected type */
+ MediaRoute2Info.TYPE_USB_DEVICE,
+ /* actual audio route type */
+ AudioRoutesInfo.MAIN_USB }
+ });
+ }
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private AudioService mAudioService;
+ @Mock
+ private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+ @IdRes
+ private final int mExpectedRouteNameResource;
+ private final String mExpectedRouteNameValue;
+ private final int mExpectedRouteType;
+ private final int mActualAudioRouteType;
+
+ public DeviceRouteInitializationTest(int expectedRouteNameResource,
+ String expectedRouteNameValue,
+ int expectedMediaRouteType,
+ int actualAudioRouteType) {
+ this.mExpectedRouteNameResource = expectedRouteNameResource;
+ this.mExpectedRouteNameValue = expectedRouteNameValue;
+ this.mExpectedRouteType = expectedMediaRouteType;
+ this.mActualAudioRouteType = actualAudioRouteType;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void initialize_wiredRouteAvailable_deviceRouteReturnsWiredRoute() {
+ // Mocking default_audio_route_name.
+ when(mResources.getText(R.string.default_audio_route_name))
+ .thenReturn(DEFAULT_ROUTE_NAME);
+
+ // At first, WiredRouteController should initialize device
+ // route based on AudioService response.
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = mActualAudioRouteType;
+ when(mAudioService.startWatchingRoutes(any())).thenReturn(audioRoutesInfo);
+
+ when(mResources.getText(mExpectedRouteNameResource))
+ .thenReturn(mExpectedRouteNameValue);
+
+ DeviceRouteController deviceRouteController = new DeviceRouteController(
+ mContext,
+ mAudioManager,
+ mAudioService,
+ mOnDeviceRouteChangedListener
+ );
+
+ MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+
+ assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType);
+ assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue))
+ .isTrue();
+ // Volume did not change, so it should be set to default value (0).
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+ }
+ }
+
+ @RunWith(JUnit4.class)
+ public static class VolumeAndDeviceRoutesChangesTest {
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private AudioService mAudioService;
+ @Mock
+ private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+ @Captor
+ private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
+
+ private DeviceRouteController mDeviceRouteController;
+ private IAudioRoutesObserver.Stub mAudioRoutesObserver;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+
+ when(mResources.getText(R.string.default_audio_route_name))
+ .thenReturn(DEFAULT_ROUTE_NAME);
+
+ // Setting built-in speaker as default speaker.
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
+ when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
+ .thenReturn(audioRoutesInfo);
+
+ mDeviceRouteController = new DeviceRouteController(
+ mContext,
+ mAudioManager,
+ mAudioService,
+ mOnDeviceRouteChangedListener
+ );
+
+ mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
+ }
+
+ @Test
+ public void newDeviceConnects_wiredDevice_deviceRouteReturnsWiredDevice() {
+ // Connecting wired headset
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+
+ when(mResources.getText(
+ com.android.internal.R.string.default_audio_route_name_headphones))
+ .thenReturn(DEFAULT_HEADPHONES_NAME);
+
+ // Simulating wired device being connected.
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+
+ assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
+ assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME))
+ .isTrue();
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+ }
+
+ @Test
+ public void newDeviceConnects_bluetoothDevice_deviceRouteReturnsBluetoothDevice() {
+ // Simulating bluetooth speaker being connected.
+ AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
+ callAudioRoutesObserver(fakeBluetoothAudioRoute);
+
+ MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+
+ assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
+ .isTrue();
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+ }
+
+ @Test
+ public void updateVolume_differentValue_updatesDeviceRouteVolume() {
+ MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+
+ assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
+
+ actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+ assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1);
+ }
+
+ @Test
+ public void updateVolume_sameValue_returnsFalse() {
+ assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
+ assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isFalse();
+ }
+
+ /**
+ * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
+ * from {@link AudioService}. This happens when there is a wired route change,
+ * like a wired headset being connected.
+ *
+ * @param audioRoutesInfo updated state of connected wired device
+ */
+ private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
+ try {
+ // this is a captured observer implementation
+ // from WiredRoutesController's AudioService#startWatchingRoutes call
+ mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
+ } catch (RemoteException exception) {
+ // Should not happen since the object is mocked.
+ assertWithMessage("An unexpected RemoteException happened.").fail();
+ }
+ }
+ }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index ce1157e..7d5750e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -30,11 +30,14 @@
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.BatteryStatsInternal;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@@ -291,6 +294,8 @@
public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
throws RemoteException {
try {
+ BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger(
+ SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
logVoidReturn("onRecognition", modelHandle, event);
} catch (Exception e) {
@@ -304,6 +309,8 @@
int captureSession)
throws RemoteException {
try {
+ BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger(
+ SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
startKeyphraseEventLatencyTracking(event);
mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
logVoidReturn("onPhraseRecognition", modelHandle, event);
@@ -387,6 +394,12 @@
}
}
+ private static class BatteryStatsHolder {
+ private static final BatteryStatsInternal INSTANCE =
+ LocalServices.getService(BatteryStatsInternal.class);
+ }
+
+
////////////////////////////////////////////////////////////////////////////////////////////////
// Actual logging logic below.
private static final int NUM_EVENTS_TO_DUMP = 64;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index b9008c4..788d0e8 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1526,7 +1526,7 @@
* Formats the specified {@code phoneNumber} to the E.164 representation.
*
* @param phoneNumber the phone number to format.
- * @param defaultCountryIso the ISO 3166-1 two letters country code.
+ * @param defaultCountryIso the ISO 3166-1 two letters country code in UPPER CASE.
* @return the E.164 representation, or null if the given phone number is not valid.
*/
public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) {
@@ -1537,7 +1537,7 @@
* Formats the specified {@code phoneNumber} to the RFC3966 representation.
*
* @param phoneNumber the phone number to format.
- * @param defaultCountryIso the ISO 3166-1 two letters country code.
+ * @param defaultCountryIso the ISO 3166-1 two letters country code in UPPER CASE.
* @return the RFC3966 representation, or null if the given phone number is not valid.
*/
public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index c352266..64454cc 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -166,8 +166,8 @@
public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
/**
- * Bundle key to get the respoonse from
- * {@link #sendSatelliteDatagram(long, int, SatelliteDatagram, Executor, OutcomeReceiver)}.
+ * Bundle key to get the respoonse from {@link
+ * #sendSatelliteDatagram(long, int, SatelliteDatagram, boolean, Executor, OutcomeReceiver)}.
* @hide
*/
public static final String KEY_SEND_SATELLITE_DATAGRAM = "send_satellite_datagram";
@@ -701,6 +701,10 @@
*/
public static final int SATELLITE_MODEM_STATE_OFF = 4;
/**
+ * Satellite modem is unavailable.
+ */
+ public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5;
+ /**
* Satellite modem state is unknown. This generic modem state should be used only when the
* modem state cannot be mapped to other specific modem states.
*/
@@ -713,6 +717,7 @@
SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
SATELLITE_MODEM_STATE_DATAGRAM_RETRYING,
SATELLITE_MODEM_STATE_OFF,
+ SATELLITE_MODEM_STATE_UNAVAILABLE,
SATELLITE_MODEM_STATE_UNKNOWN
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 5dc1a65..d93ee21 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -31,7 +31,6 @@
* Register the callback interface with satellite service.
*
* @param listener The callback interface to handle satellite service indications.
- * @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -43,7 +42,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void setSatelliteListener(in ISatelliteListener listener, in IIntegerConsumer errorCallback);
+ void setSatelliteListener(in ISatelliteListener listener);
/**
* Request to enable or disable the satellite service listening mode.
@@ -51,6 +50,8 @@
*
* @param enable True to enable satellite listening mode and false to disable.
* @param isDemoMode Whether demo mode is enabled.
+ * @param timeout How long the satellite modem should wait for the next incoming page before
+ * disabling listening mode.
* @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -63,7 +64,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode,
+ void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout,
in IIntegerConsumer errorCallback);
/**
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index e24e892e..d966868 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -36,10 +36,10 @@
/**
* Indicates that new datagrams have been received on the device.
*
- * @param datagrams Array of new datagrams received.
- * @param pendingCount The number of datagrams that are pending.
+ * @param datagram New datagram that was received.
+ * @param pendingCount Number of additional datagrams yet to be received.
*/
- void onSatelliteDatagramsReceived(in SatelliteDatagram[] datagrams, in int pendingCount);
+ void onSatelliteDatagramReceived(in SatelliteDatagram datagram, in int pendingCount);
/**
* Indicates that the satellite has pending datagrams for the device to be pulled.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index df51432..711dcbe 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -63,19 +63,19 @@
private final IBinder mBinder = new ISatellite.Stub() {
@Override
- public void setSatelliteListener(ISatelliteListener listener,
- IIntegerConsumer errorCallback) throws RemoteException {
+ public void setSatelliteListener(ISatelliteListener listener) throws RemoteException {
executeMethodAsync(
- () -> SatelliteImplBase.this.setSatelliteListener(listener, errorCallback),
+ () -> SatelliteImplBase.this.setSatelliteListener(listener),
"setSatelliteListener");
}
@Override
public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
- IIntegerConsumer errorCallback) throws RemoteException {
+ int timeout, IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
- .requestSatelliteListeningEnabled(enable, isDemoMode, errorCallback),
+ .requestSatelliteListeningEnabled(
+ enable, isDemoMode, timeout, errorCallback),
"requestSatelliteListeningEnabled");
}
@@ -229,7 +229,6 @@
* Register the callback interface with satellite service.
*
* @param listener The callback interface to handle satellite service indications.
- * @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
* SatelliteError:ERROR_NONE
@@ -241,8 +240,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- public void setSatelliteListener(@NonNull ISatelliteListener listener,
- @NonNull IIntegerConsumer errorCallback) {
+ public void setSatelliteListener(@NonNull ISatelliteListener listener) {
// stub implementation
}
@@ -252,6 +250,8 @@
*
* @param enable True to enable satellite listening mode and false to disable.
* @param isDemoMode Whether demo mode is enabled.
+ * @param timeout How long the satellite modem should wait for the next incoming page before
+ * disabling listening mode.
* @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -264,7 +264,7 @@
* SatelliteError:REQUEST_NOT_SUPPORTED
* SatelliteError:NO_RESOURCES
*/
- public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
+ public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout,
@NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
index 5ee7f9a..e4f9413 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
@@ -42,6 +42,10 @@
*/
SATELLITE_MODEM_STATE_OFF = 4,
/**
+ * Satellite modem is unavailable.
+ */
+ SATELLITE_MODEM_STATE_UNAVAILABLE = 5,
+ /**
* Satellite modem state is unknown. This generic modem state should be used only when the
* modem state cannot be mapped to other specific modem states.
*/
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index c7e5a5e..e59071b 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -29,6 +29,7 @@
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Debug.MemoryInfo;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
@@ -40,6 +41,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
/**
* This test is intended to measure the amount of memory applications use when
@@ -313,17 +315,19 @@
public void run() {
try {
- String mimeType = mLaunchIntent.getType();
- if (mimeType == null && mLaunchIntent.getData() != null
+ AtomicReference<String> mimeType = new AtomicReference<>(mLaunchIntent.getType());
+ if (mimeType.get() == null && mLaunchIntent.getData() != null
&& "content".equals(mLaunchIntent.getData().getScheme())) {
- mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
- UserHandle.USER_CURRENT);
+ mAm.getMimeTypeFilterAsync(mLaunchIntent.getData(), UserHandle.USER_CURRENT,
+ new RemoteCallback(result -> {
+ mimeType.set(result.getPairValue());
+ }));
}
mAtm.startActivityAndWait(null,
getInstrumentation().getContext().getBasePackageName(),
getInstrumentation().getContext().getAttributionTag(), mLaunchIntent,
- mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null,
+ mimeType.get(), null, null, 0, mLaunchIntent.getFlags(), null, null,
UserHandle.USER_CURRENT_OR_SELF);
} catch (RemoteException e) {
Log.w(TAG, "Error launching app", e);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
index 161c83c..1fb1c63 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -24,11 +24,12 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.ArraySet;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Objects;
+import java.util.Set;
/**
* A data class representing a known Wi-Fi network.
@@ -59,7 +60,7 @@
@NetworkSource private final int mNetworkSource;
private final String mSsid;
- @SecurityType private final int[] mSecurityTypes;
+ @SecurityType private final ArraySet<Integer> mSecurityTypes;
private final DeviceInfo mDeviceInfo;
/**
@@ -68,11 +69,9 @@
public static final class Builder {
@NetworkSource private int mNetworkSource = -1;
private String mSsid;
- @SecurityType private int[] mSecurityTypes;
+ @SecurityType private final ArraySet<Integer> mSecurityTypes = new ArraySet<>();
private android.net.wifi.sharedconnectivity.app.DeviceInfo mDeviceInfo;
- public Builder() {}
-
/**
* Sets the indicated source of the known network.
*
@@ -98,14 +97,14 @@
}
/**
- * Sets the security types of the known network.
+ * Adds a security type of the known network.
*
- * @param securityTypes The array of security types supported by the known network.
+ * @param securityType A security type supported by the known network.
* @return Returns the Builder object.
*/
@NonNull
- public Builder setSecurityTypes(@NonNull @SecurityType int[] securityTypes) {
- mSecurityTypes = securityTypes;
+ public Builder addSecurityType(@SecurityType int securityType) {
+ mSecurityTypes.add(securityType);
return this;
}
@@ -136,7 +135,7 @@
}
}
- private static void validate(int networkSource, String ssid, int [] securityTypes) {
+ private static void validate(int networkSource, String ssid, Set<Integer> securityTypes) {
if (networkSource != NETWORK_SOURCE_CLOUD_SELF && networkSource
!= NETWORK_SOURCE_NEARBY_SELF) {
throw new IllegalArgumentException("Illegal network source");
@@ -144,7 +143,7 @@
if (TextUtils.isEmpty(ssid)) {
throw new IllegalArgumentException("SSID must be set");
}
- if (securityTypes == null || securityTypes.length == 0) {
+ if (securityTypes.isEmpty()) {
throw new IllegalArgumentException("SecurityTypes must be set");
}
}
@@ -152,12 +151,12 @@
private KnownNetwork(
@NetworkSource int networkSource,
@NonNull String ssid,
- @NonNull @SecurityType int[] securityTypes,
+ @NonNull @SecurityType ArraySet<Integer> securityTypes,
@NonNull DeviceInfo deviceInfo) {
validate(networkSource, ssid, securityTypes);
mNetworkSource = networkSource;
mSsid = ssid;
- mSecurityTypes = securityTypes;
+ mSecurityTypes = new ArraySet<>(securityTypes);
mDeviceInfo = deviceInfo;
}
@@ -184,11 +183,11 @@
/**
* Gets the security types of the known network.
*
- * @return Returns the array of security types supported by the known network.
+ * @return Returns a set with security types supported by the known network.
*/
@NonNull
@SecurityType
- public int[] getSecurityTypes() {
+ public Set<Integer> getSecurityTypes() {
return mSecurityTypes;
}
@@ -208,14 +207,13 @@
KnownNetwork other = (KnownNetwork) obj;
return mNetworkSource == other.getNetworkSource()
&& Objects.equals(mSsid, other.getSsid())
- && Arrays.equals(mSecurityTypes, other.getSecurityTypes())
+ && Objects.equals(mSecurityTypes, other.getSecurityTypes())
&& Objects.equals(mDeviceInfo, other.getDeviceInfo());
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkSource, mSsid, Arrays.hashCode(mSecurityTypes),
- mDeviceInfo.hashCode());
+ return Objects.hash(mNetworkSource, mSsid, mSecurityTypes, mDeviceInfo);
}
@Override
@@ -227,7 +225,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mNetworkSource);
dest.writeString(mSsid);
- dest.writeIntArray(mSecurityTypes);
+ dest.writeArraySet(mSecurityTypes);
mDeviceInfo.writeToParcel(dest, flags);
}
@@ -238,7 +236,8 @@
*/
@NonNull
public static KnownNetwork readFromParcel(@NonNull Parcel in) {
- return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(),
+ return new KnownNetwork(in.readInt(), in.readString(),
+ (ArraySet<Integer>) in.readArraySet(null),
DeviceInfo.readFromParcel(in));
}
@@ -260,7 +259,7 @@
return new StringBuilder("KnownNetwork[")
.append("NetworkSource=").append(mNetworkSource)
.append(", ssid=").append(mSsid)
- .append(", securityTypes=").append(Arrays.toString(mSecurityTypes))
+ .append(", securityTypes=").append(mSecurityTypes.toString())
.append(", deviceInfo=").append(mDeviceInfo.toString())
.append("]").toString();
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
index af4fd4a..7b591d3 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
@@ -25,12 +25,12 @@
import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
import android.os.Parcel;
import android.os.Parcelable;
-
+import android.util.ArraySet;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Objects;
+import java.util.Set;
/**
* A data class representing an Instant Tether network.
@@ -79,7 +79,7 @@
private final String mNetworkName;
@Nullable private final String mHotspotSsid;
@Nullable private final String mHotspotBssid;
- @Nullable @SecurityType private final int[] mHotspotSecurityTypes;
+ @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes;
/**
* Builder class for {@link TetherNetwork}.
@@ -91,9 +91,8 @@
private String mNetworkName;
@Nullable private String mHotspotSsid;
@Nullable private String mHotspotBssid;
- @Nullable @SecurityType private int[] mHotspotSecurityTypes;
-
- public Builder() {}
+ @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes =
+ new ArraySet<>();
/**
* Set the remote device ID.
@@ -168,15 +167,14 @@
}
/**
- * Sets the hotspot security types supported by the remote device, or null if hotspot is
- * off.
+ * Adds a security type supported by the hotspot created by the remote device.
*
- * @param hotspotSecurityTypes The array of security types supported by the hotspot.
+ * @param hotspotSecurityType A security type supported by the hotspot.
* @return Returns the Builder object.
*/
@NonNull
- public Builder setHotspotSecurityTypes(@NonNull @SecurityType int[] hotspotSecurityTypes) {
- mHotspotSecurityTypes = hotspotSecurityTypes;
+ public Builder addHotspotSecurityType(@SecurityType int hotspotSecurityType) {
+ mHotspotSecurityTypes.add(hotspotSecurityType);
return this;
}
@@ -218,7 +216,7 @@
@NonNull String networkName,
@Nullable String hotspotSsid,
@Nullable String hotspotBssid,
- @Nullable @SecurityType int[] hotspotSecurityTypes) {
+ @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes) {
validate(deviceId,
networkType,
networkName);
@@ -228,7 +226,7 @@
mNetworkName = networkName;
mHotspotSsid = hotspotSsid;
mHotspotBssid = hotspotBssid;
- mHotspotSecurityTypes = hotspotSecurityTypes;
+ mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes);
}
/**
@@ -293,11 +291,11 @@
/**
* Gets the hotspot security types supported by the remote device.
*
- * @return Returns the array of security types supported by the hotspot.
+ * @return Returns a set of the security types supported by the hotspot.
*/
- @Nullable
+ @NonNull
@SecurityType
- public int[] getHotspotSecurityTypes() {
+ public Set<Integer> getHotspotSecurityTypes() {
return mHotspotSecurityTypes;
}
@@ -311,13 +309,13 @@
&& Objects.equals(mNetworkName, other.getNetworkName())
&& Objects.equals(mHotspotSsid, other.getHotspotSsid())
&& Objects.equals(mHotspotBssid, other.getHotspotBssid())
- && Arrays.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes());
+ && Objects.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes());
}
@Override
public int hashCode() {
return Objects.hash(mDeviceId, mDeviceInfo, mNetworkName, mHotspotSsid, mHotspotBssid,
- Arrays.hashCode(mHotspotSecurityTypes));
+ mHotspotSecurityTypes);
}
@Override
@@ -333,7 +331,7 @@
dest.writeString(mNetworkName);
dest.writeString(mHotspotSsid);
dest.writeString(mHotspotBssid);
- dest.writeIntArray(mHotspotSecurityTypes);
+ dest.writeArraySet(mHotspotSecurityTypes);
}
/**
@@ -345,7 +343,7 @@
public static TetherNetwork readFromParcel(@NonNull Parcel in) {
return new TetherNetwork(in.readLong(), DeviceInfo.readFromParcel(in),
in.readInt(), in.readString(), in.readString(), in.readString(),
- in.createIntArray());
+ (ArraySet<Integer>) in.readArraySet(null));
}
@NonNull
@@ -370,7 +368,7 @@
.append(", networkName=").append(mNetworkName)
.append(", hotspotSsid=").append(mHotspotSsid)
.append(", hotspotBssid=").append(mHotspotBssid)
- .append(", hotspotSecurityTypes=").append(Arrays.toString(mHotspotSecurityTypes))
+ .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString())
.append("]").toString();
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
index f8f0700..e6595eb 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
@@ -19,8 +19,7 @@
import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_LAPTOP;
import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
@@ -29,7 +28,7 @@
import org.junit.Test;
/**
- * Unit tests for {@link android.app.sharedconnectivity.DeviceInfo}.
+ * Unit tests for {@link DeviceInfo}.
*/
@SmallTest
public class DeviceInfoTest {
@@ -63,8 +62,8 @@
parcelR.setDataPosition(0);
DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcelR);
- assertEquals(info, fromParcel);
- assertEquals(info.hashCode(), fromParcel.hashCode());
+ assertThat(fromParcel).isEqualTo(info);
+ assertThat(fromParcel.hashCode()).isEqualTo(info.hashCode());
}
/**
@@ -74,24 +73,24 @@
public void testEqualsOperation() {
DeviceInfo info1 = buildDeviceInfoBuilder().build();
DeviceInfo info2 = buildDeviceInfoBuilder().build();
- assertEquals(info1, info2);
+ assertThat(info1).isEqualTo(info2);
DeviceInfo.Builder builder = buildDeviceInfoBuilder().setDeviceType(DEVICE_TYPE_1);
- assertNotEquals(info1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(info1);
builder = buildDeviceInfoBuilder().setDeviceName(DEVICE_NAME_1);
- assertNotEquals(info1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(info1);
builder = buildDeviceInfoBuilder().setModelName(DEVICE_MODEL_1);
- assertNotEquals(info1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(info1);
builder = buildDeviceInfoBuilder()
.setBatteryPercentage(BATTERY_PERCENTAGE_1);
- assertNotEquals(info1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(info1);
builder = buildDeviceInfoBuilder()
.setConnectionStrength(CONNECTION_STRENGTH_1);
- assertNotEquals(info1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(info1);
}
/**
@@ -100,12 +99,19 @@
@Test
public void testGetMethods() {
DeviceInfo info = buildDeviceInfoBuilder().build();
- assertEquals(info.getDeviceType(), DEVICE_TYPE);
- assertEquals(info.getDeviceName(), DEVICE_NAME);
- assertEquals(info.getModelName(), DEVICE_MODEL);
- assertEquals(info.getBatteryPercentage(), BATTERY_PERCENTAGE);
- assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH);
- assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH);
+ assertThat(info.getDeviceType()).isEqualTo(DEVICE_TYPE);
+ assertThat(info.getDeviceName()).isEqualTo(DEVICE_NAME);
+ assertThat(info.getModelName()).isEqualTo(DEVICE_MODEL);
+ assertThat(info.getBatteryPercentage()).isEqualTo(BATTERY_PERCENTAGE);
+ assertThat(info.getConnectionStrength()).isEqualTo(CONNECTION_STRENGTH);
+ }
+
+ @Test
+ public void testHashCode() {
+ DeviceInfo info1 = buildDeviceInfoBuilder().build();
+ DeviceInfo info2 = buildDeviceInfoBuilder().build();
+
+ assertThat(info1.hashCode()).isEqualTo(info2.hashCode());
}
private DeviceInfo.Builder buildDeviceInfoBuilder() {
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
index 37dca8d..8a0f21e 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
@@ -22,8 +22,7 @@
import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED;
import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVE_FAILED;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import android.os.Parcel;
@@ -32,8 +31,10 @@
import org.junit.Test;
+import java.util.Arrays;
+
/**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus}.
+ * Unit tests for {@link KnownNetworkConnectionStatus}.
*/
@SmallTest
public class KnownNetworkConnectionStatusTest {
@@ -45,6 +46,7 @@
.setConnectionStrength(2).setBatteryPercentage(50).build();
private static final String SSID_1 = "TEST_SSID1";
private static final String BUNDLE_KEY = "INT-KEY";
+ private static final int BUNDLE_VALUE = 1;
/**
* Verifies parcel serialization/deserialization.
@@ -64,8 +66,8 @@
KnownNetworkConnectionStatus fromParcel =
KnownNetworkConnectionStatus.CREATOR.createFromParcel(parcelR);
- assertEquals(status, fromParcel);
- assertEquals(status.hashCode(), fromParcel.hashCode());
+ assertThat(fromParcel).isEqualTo(status);
+ assertThat(fromParcel.hashCode()).isEqualTo(status.hashCode());
}
/**
@@ -75,15 +77,15 @@
public void testEqualsOperation() {
KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
- assertEquals(status2, status2);
+ assertThat(status1).isEqualTo(status2);
KnownNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder()
.setStatus(CONNECTION_STATUS_SAVE_FAILED);
- assertNotEquals(status1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(status1);
builder = buildConnectionStatusBuilder()
.setKnownNetwork(buildKnownNetworkBuilder().setSsid(SSID_1).build());
- assertNotEquals(status1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(status1);
}
/**
@@ -92,9 +94,17 @@
@Test
public void testGetMethods() {
KnownNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
- assertEquals(status.getStatus(), CONNECTION_STATUS_SAVED);
- assertEquals(status.getKnownNetwork(), buildKnownNetworkBuilder().build());
- assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY));
+ assertThat(status.getStatus()).isEqualTo(CONNECTION_STATUS_SAVED);
+ assertThat(status.getKnownNetwork()).isEqualTo(buildKnownNetworkBuilder().build());
+ assertThat(status.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
+ }
+
+ @Test
+ public void testHashCode() {
+ KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
+ KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
+
+ assertThat(status1.hashCode()).isEqualTo(status2.hashCode());
}
private KnownNetworkConnectionStatus.Builder buildConnectionStatusBuilder() {
@@ -106,13 +116,15 @@
private Bundle buildBundle() {
Bundle bundle = new Bundle();
- bundle.putInt(BUNDLE_KEY, 1);
+ bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
return bundle;
}
private KnownNetwork.Builder buildKnownNetworkBuilder() {
- return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
- .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO);
+ KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
+ .setSsid(SSID).setDeviceInfo(DEVICE_INFO);
+ Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
+ return builder;
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
index 266afcc..872dd2e 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
@@ -23,18 +23,19 @@
import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_CLOUD_SELF;
import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import org.junit.Test;
+import java.util.Arrays;
+
/**
- * Unit tests for {@link android.app.sharedconnectivity.KnownNetwork}.
+ * Unit tests for {@link KnownNetwork}.
*/
@SmallTest
public class KnownNetworkTest {
@@ -69,8 +70,8 @@
parcelR.setDataPosition(0);
KnownNetwork fromParcel = KnownNetwork.CREATOR.createFromParcel(parcelR);
- assertEquals(network, fromParcel);
- assertEquals(network.hashCode(), fromParcel.hashCode());
+ assertThat(fromParcel).isEqualTo(network);
+ assertThat(fromParcel.hashCode()).isEqualTo(network.hashCode());
}
/**
@@ -80,20 +81,21 @@
public void testEqualsOperation() {
KnownNetwork network1 = buildKnownNetworkBuilder().build();
KnownNetwork network2 = buildKnownNetworkBuilder().build();
- assertEquals(network1, network2);
+ assertThat(network1).isEqualTo(network2);
KnownNetwork.Builder builder = buildKnownNetworkBuilder()
.setNetworkSource(NETWORK_SOURCE_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildKnownNetworkBuilder().setSsid(SSID_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
- builder = buildKnownNetworkBuilder().setSecurityTypes(SECURITY_TYPES_1);
- assertNotEquals(network1, builder.build());
+ builder = buildKnownNetworkBuilder();
+ Arrays.stream(SECURITY_TYPES_1).forEach(builder::addSecurityType);
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildKnownNetworkBuilder().setDeviceInfo(DEVICE_INFO_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
}
/**
@@ -102,14 +104,27 @@
@Test
public void testGetMethods() {
KnownNetwork network = buildKnownNetworkBuilder().build();
- assertEquals(network.getNetworkSource(), NETWORK_SOURCE);
- assertEquals(network.getSsid(), SSID);
- assertArrayEquals(network.getSecurityTypes(), SECURITY_TYPES);
- assertEquals(network.getDeviceInfo(), DEVICE_INFO);
+ ArraySet<Integer> securityTypes = new ArraySet<>();
+ Arrays.stream(SECURITY_TYPES).forEach(securityTypes::add);
+
+ assertThat(network.getNetworkSource()).isEqualTo(NETWORK_SOURCE);
+ assertThat(network.getSsid()).isEqualTo(SSID);
+ assertThat(network.getSecurityTypes()).containsExactlyElementsIn(securityTypes);
+ assertThat(network.getDeviceInfo()).isEqualTo(DEVICE_INFO);
+ }
+
+ @Test
+ public void testHashCode() {
+ KnownNetwork network1 = buildKnownNetworkBuilder().build();
+ KnownNetwork network2 = buildKnownNetworkBuilder().build();
+
+ assertThat(network1.hashCode()).isEqualTo(network2.hashCode());
}
private KnownNetwork.Builder buildKnownNetworkBuilder() {
- return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
- .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO);
+ KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
+ .setSsid(SSID).setDeviceInfo(DEVICE_INFO);
+ Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
+ return builder;
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index cdb438f..7c0a8b6 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -22,11 +22,8 @@
import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
@@ -49,6 +46,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
@@ -110,7 +108,7 @@
public void resourcesNotDefined() {
when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException());
- assertNull(SharedConnectivityManager.create(mContext));
+ assertThat(SharedConnectivityManager.create(mContext)).isNull();
}
/**
@@ -183,7 +181,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertFalse(manager.unregisterCallback(mClientCallback));
+ assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
}
@Test
@@ -191,7 +189,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(mService);
- assertFalse(manager.unregisterCallback(mClientCallback));
+ assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
}
@Test
@@ -201,7 +199,7 @@
manager.registerCallback(mExecutor, mClientCallback);
- assertTrue(manager.unregisterCallback(mClientCallback));
+ assertThat(manager.unregisterCallback(mClientCallback)).isTrue();
verify(mService).unregisterCallback(any());
}
@@ -213,7 +211,7 @@
manager.registerCallback(mExecutor, mClientCallback);
manager.unregisterCallback(mClientCallback);
- assertFalse(manager.unregisterCallback(mClientCallback));
+ assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
}
@Test
@@ -224,7 +222,7 @@
manager.registerCallback(mExecutor, mClientCallback);
manager.unregisterCallback(mClientCallback);
- assertFalse(manager.unregisterCallback(mClientCallback));
+ assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
}
@Test
@@ -234,7 +232,7 @@
doThrow(new RemoteException()).when(mService).unregisterCallback(any());
- assertFalse(manager.unregisterCallback(mClientCallback));
+ assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
}
/**
@@ -294,7 +292,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertFalse(manager.connectTetherNetwork(network));
+ assertThat(manager.connectTetherNetwork(network)).isFalse();
}
@Test
@@ -315,7 +313,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).connectTetherNetwork(network);
- assertFalse(manager.connectTetherNetwork(network));
+ assertThat(manager.connectTetherNetwork(network)).isFalse();
}
/**
@@ -327,7 +325,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertFalse(manager.disconnectTetherNetwork(network));
+ assertThat(manager.disconnectTetherNetwork(network)).isFalse();
}
@Test
@@ -348,7 +346,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).disconnectTetherNetwork(any());
- assertFalse(manager.disconnectTetherNetwork(network));
+ assertThat(manager.disconnectTetherNetwork(network)).isFalse();
}
/**
@@ -360,7 +358,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertFalse(manager.connectKnownNetwork(network));
+ assertThat(manager.connectKnownNetwork(network)).isFalse();
}
@Test
@@ -381,7 +379,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).connectKnownNetwork(network);
- assertFalse(manager.connectKnownNetwork(network));
+ assertThat(manager.connectKnownNetwork(network)).isFalse();
}
/**
@@ -393,7 +391,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertFalse(manager.forgetKnownNetwork(network));
+ assertThat(manager.forgetKnownNetwork(network)).isFalse();
}
@Test
@@ -414,7 +412,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).forgetKnownNetwork(network);
- assertFalse(manager.forgetKnownNetwork(network));
+ assertThat(manager.forgetKnownNetwork(network)).isFalse();
}
/**
@@ -425,7 +423,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertArrayEquals(List.of().toArray(), manager.getTetherNetworks().toArray());
+ assertThat(manager.getKnownNetworks()).isEmpty();
}
@Test
@@ -434,18 +432,17 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getTetherNetworks();
- assertArrayEquals(List.of().toArray(), manager.getTetherNetworks().toArray());
+ assertThat(manager.getKnownNetworks()).isEmpty();
}
@Test
public void getTetherNetworks_shouldReturnNetworksList() throws RemoteException {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
List<TetherNetwork> networks = List.of(buildTetherNetwork());
- List<TetherNetwork> expected = List.of(buildTetherNetwork());
manager.setService(mService);
when(mService.getTetherNetworks()).thenReturn(networks);
- assertArrayEquals(expected.toArray(), manager.getTetherNetworks().toArray());
+ assertThat(manager.getTetherNetworks()).containsExactly(buildTetherNetwork());
}
@Test
@@ -454,7 +451,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertArrayEquals(List.of().toArray(), manager.getKnownNetworks().toArray());
+ assertThat(manager.getKnownNetworks()).isEmpty();
}
@Test
@@ -463,18 +460,17 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getKnownNetworks();
- assertArrayEquals(List.of().toArray(), manager.getKnownNetworks().toArray());
+ assertThat(manager.getKnownNetworks()).isEmpty();
}
@Test
public void getKnownNetworks_shouldReturnNetworksList() throws RemoteException {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
List<KnownNetwork> networks = List.of(buildKnownNetwork());
- List<KnownNetwork> expected = List.of(buildKnownNetwork());
manager.setService(mService);
when(mService.getKnownNetworks()).thenReturn(networks);
- assertArrayEquals(expected.toArray(), manager.getKnownNetworks().toArray());
+ assertThat(manager.getKnownNetworks()).containsExactly(buildKnownNetwork());
}
@Test
@@ -482,7 +478,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertNull(manager.getSettingsState());
+ assertThat(manager.getSettingsState()).isNull();
}
@Test
@@ -491,7 +487,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getSettingsState();
- assertNull(manager.getSettingsState());
+ assertThat(manager.getSettingsState()).isNull();
}
@Test
@@ -502,7 +498,7 @@
manager.setService(mService);
when(mService.getSettingsState()).thenReturn(state);
- assertEquals(state, manager.getSettingsState());
+ assertThat(manager.getSettingsState()).isEqualTo(state);
}
@Test
@@ -511,7 +507,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertNull(manager.getTetherNetworkConnectionStatus());
+ assertThat(manager.getTetherNetworkConnectionStatus()).isNull();
}
@Test
@@ -521,7 +517,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getTetherNetworkConnectionStatus();
- assertNull(manager.getTetherNetworkConnectionStatus());
+ assertThat(manager.getTetherNetworkConnectionStatus()).isNull();
}
@Test
@@ -534,7 +530,7 @@
manager.setService(mService);
when(mService.getTetherNetworkConnectionStatus()).thenReturn(status);
- assertEquals(status, manager.getTetherNetworkConnectionStatus());
+ assertThat(manager.getTetherNetworkConnectionStatus()).isEqualTo(status);
}
@Test
@@ -543,7 +539,7 @@
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.setService(null);
- assertNull(manager.getKnownNetworkConnectionStatus());
+ assertThat(manager.getKnownNetworkConnectionStatus()).isNull();
}
@Test
@@ -553,7 +549,7 @@
manager.setService(mService);
doThrow(new RemoteException()).when(mService).getKnownNetworkConnectionStatus();
- assertNull(manager.getKnownNetworkConnectionStatus());
+ assertThat(manager.getKnownNetworkConnectionStatus()).isNull();
}
@Test
@@ -566,7 +562,7 @@
manager.setService(mService);
when(mService.getKnownNetworkConnectionStatus()).thenReturn(status);
- assertEquals(status, manager.getKnownNetworkConnectionStatus());
+ assertThat(manager.getKnownNetworkConnectionStatus()).isEqualTo(status);
}
private void setResources(@Mock Context context) {
@@ -576,18 +572,20 @@
}
private TetherNetwork buildTetherNetwork() {
- return new TetherNetwork.Builder()
+ TetherNetwork.Builder builder = new TetherNetwork.Builder()
.setDeviceId(DEVICE_ID)
.setDeviceInfo(DEVICE_INFO)
.setNetworkType(NETWORK_TYPE)
.setNetworkName(NETWORK_NAME)
- .setHotspotSsid(HOTSPOT_SSID)
- .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES)
- .build();
+ .setHotspotSsid(HOTSPOT_SSID);
+ Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
+ return builder.build();
}
private KnownNetwork buildKnownNetwork() {
- return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
- .setSecurityTypes(SECURITY_TYPES).build();
+ KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
+ .setSsid(SSID).setDeviceInfo(DEVICE_INFO);
+ Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
+ return builder.build();
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
index 3137c72..752b749 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
@@ -16,8 +16,7 @@
package android.net.wifi.sharedconnectivity.app;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
@@ -26,7 +25,7 @@
import org.junit.Test;
/**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState}.
+ * Unit tests for {@link SharedConnectivitySettingsState}.
*/
@SmallTest
public class SharedConnectivitySettingsStateTest {
@@ -51,8 +50,8 @@
SharedConnectivitySettingsState fromParcel =
SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR);
- assertEquals(state, fromParcel);
- assertEquals(state.hashCode(), fromParcel.hashCode());
+ assertThat(fromParcel).isEqualTo(state);
+ assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode());
}
/**
@@ -62,11 +61,11 @@
public void testEqualsOperation() {
SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build();
SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build();
- assertEquals(state1, state2);
+ assertThat(state1).isEqualTo(state2);
SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder()
.setInstantTetherEnabled(INSTANT_TETHER_STATE_1);
- assertNotEquals(state1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(state1);
}
/**
@@ -75,7 +74,15 @@
@Test
public void testGetMethods() {
SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
- assertEquals(state.isInstantTetherEnabled(), INSTANT_TETHER_STATE);
+ assertThat(state.isInstantTetherEnabled()).isEqualTo(INSTANT_TETHER_STATE);
+ }
+
+ @Test
+ public void testHashCode() {
+ SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build();
+ SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build();
+
+ assertThat(state1.hashCode()).isEqualTo(state2.hashCode());
}
private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() {
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
index 1d9c2e6..0844364 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
@@ -23,8 +23,7 @@
import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT;
import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import android.os.Parcel;
@@ -33,8 +32,10 @@
import org.junit.Test;
+import java.util.Arrays;
+
/**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus}.
+ * Unit tests for {@link TetherNetworkConnectionStatus}.
*/
@SmallTest
public class TetherNetworkConnectionStatusTest {
@@ -49,6 +50,7 @@
private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
private static final long DEVICE_ID_1 = 111L;
private static final String BUNDLE_KEY = "INT-KEY";
+ private static final int BUNDLE_VALUE = 1;
/**
* Verifies parcel serialization/deserialization.
@@ -68,8 +70,8 @@
TetherNetworkConnectionStatus fromParcel =
TetherNetworkConnectionStatus.CREATOR.createFromParcel(parcelR);
- assertEquals(status, fromParcel);
- assertEquals(status.hashCode(), fromParcel.hashCode());
+ assertThat(fromParcel).isEqualTo(status);
+ assertThat(fromParcel.hashCode()).isEqualTo(status.hashCode());
}
/**
@@ -79,15 +81,15 @@
public void testEqualsOperation() {
TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
- assertEquals(status2, status2);
+ assertThat(status1).isEqualTo(status2);
TetherNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder()
.setStatus(CONNECTION_STATUS_TETHERING_TIMEOUT);
- assertNotEquals(status1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(status1);
builder = buildConnectionStatusBuilder()
.setTetherNetwork(buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1).build());
- assertNotEquals(status1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(status1);
}
/**
@@ -96,13 +98,20 @@
@Test
public void testGetMethods() {
TetherNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
- assertEquals(status.getStatus(), CONNECTION_STATUS_ENABLING_HOTSPOT);
- assertEquals(status.getTetherNetwork(), buildTetherNetworkBuilder().build());
- assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY));
+ assertThat(status.getStatus()).isEqualTo(CONNECTION_STATUS_ENABLING_HOTSPOT);
+ assertThat(status.getTetherNetwork()).isEqualTo(buildTetherNetworkBuilder().build());
+ assertThat(status.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
+ }
+
+ @Test
+ public void testHashCode() {
+ TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
+ TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
+
+ assertThat(status1.hashCode()).isEqualTo(status2.hashCode());
}
private TetherNetworkConnectionStatus.Builder buildConnectionStatusBuilder() {
-
return new TetherNetworkConnectionStatus.Builder()
.setStatus(CONNECTION_STATUS_ENABLING_HOTSPOT)
.setTetherNetwork(buildTetherNetworkBuilder().build())
@@ -111,18 +120,19 @@
private Bundle buildBundle() {
Bundle bundle = new Bundle();
- bundle.putInt(BUNDLE_KEY, 1);
+ bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
return bundle;
}
private TetherNetwork.Builder buildTetherNetworkBuilder() {
- return new TetherNetwork.Builder()
+ TetherNetwork.Builder builder = new TetherNetwork.Builder()
.setDeviceId(DEVICE_ID)
.setDeviceInfo(DEVICE_INFO)
.setNetworkType(NETWORK_TYPE)
.setNetworkName(NETWORK_NAME)
.setHotspotSsid(HOTSPOT_SSID)
- .setHotspotBssid(HOTSPOT_BSSID)
- .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES);
+ .setHotspotBssid(HOTSPOT_BSSID);
+ Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
+ return builder;
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
index b01aec4..a50d767 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
@@ -24,18 +24,19 @@
import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_WIFI;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import org.junit.Test;
+import java.util.Arrays;
+
/**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetwork}.
+ * Unit tests for {@link TetherNetwork}.
*/
@SmallTest
public class TetherNetworkTest {
@@ -76,8 +77,8 @@
parcelR.setDataPosition(0);
TetherNetwork fromParcel = TetherNetwork.CREATOR.createFromParcel(parcelR);
- assertEquals(network, fromParcel);
- assertEquals(network.hashCode(), fromParcel.hashCode());
+ assertThat(fromParcel).isEqualTo(network);
+ assertThat(fromParcel.hashCode()).isEqualTo(network.hashCode());
}
/**
@@ -87,28 +88,31 @@
public void testEqualsOperation() {
TetherNetwork network1 = buildTetherNetworkBuilder().build();
TetherNetwork network2 = buildTetherNetworkBuilder().build();
- assertEquals(network1, network2);
+ assertThat(network1).isEqualTo(network2);
TetherNetwork.Builder builder = buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildTetherNetworkBuilder().setDeviceInfo(DEVICE_INFO_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildTetherNetworkBuilder().setNetworkType(NETWORK_TYPE_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildTetherNetworkBuilder().setNetworkName(NETWORK_NAME_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildTetherNetworkBuilder().setHotspotSsid(HOTSPOT_SSID_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
builder = buildTetherNetworkBuilder().setHotspotBssid(HOTSPOT_BSSID_1);
- assertNotEquals(network1, builder.build());
+ assertThat(builder.build()).isNotEqualTo(network1);
- builder = buildTetherNetworkBuilder().setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES_1);
- assertNotEquals(network1, builder.build());
+ builder = buildTetherNetworkBuilder();
+ TetherNetwork.Builder builder1 = buildTetherNetworkBuilder();
+ Arrays.stream(HOTSPOT_SECURITY_TYPES_1).forEach(builder1::addHotspotSecurityType);
+
+ assertThat(builder1.build()).isNotEqualTo(builder.build());
}
/**
@@ -117,23 +121,35 @@
@Test
public void testGetMethods() {
TetherNetwork network = buildTetherNetworkBuilder().build();
- assertEquals(network.getDeviceId(), DEVICE_ID);
- assertEquals(network.getDeviceInfo(), DEVICE_INFO);
- assertEquals(network.getNetworkType(), NETWORK_TYPE);
- assertEquals(network.getNetworkName(), NETWORK_NAME);
- assertEquals(network.getHotspotSsid(), HOTSPOT_SSID);
- assertEquals(network.getHotspotBssid(), HOTSPOT_BSSID);
- assertArrayEquals(network.getHotspotSecurityTypes(), HOTSPOT_SECURITY_TYPES);
+ ArraySet<Integer> securityTypes = new ArraySet<>();
+ Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(securityTypes::add);
+
+ assertThat(network.getDeviceId()).isEqualTo(DEVICE_ID);
+ assertThat(network.getDeviceInfo()).isEqualTo(DEVICE_INFO);
+ assertThat(network.getNetworkType()).isEqualTo(NETWORK_TYPE);
+ assertThat(network.getNetworkName()).isEqualTo(NETWORK_NAME);
+ assertThat(network.getHotspotSsid()).isEqualTo(HOTSPOT_SSID);
+ assertThat(network.getHotspotBssid()).isEqualTo(HOTSPOT_BSSID);
+ assertThat(network.getHotspotSecurityTypes()).containsExactlyElementsIn(securityTypes);
+ }
+
+ @Test
+ public void testHashCode() {
+ TetherNetwork network1 = buildTetherNetworkBuilder().build();
+ TetherNetwork network2 = buildTetherNetworkBuilder().build();
+
+ assertThat(network1.hashCode()).isEqualTo(network2.hashCode());
}
private TetherNetwork.Builder buildTetherNetworkBuilder() {
- return new TetherNetwork.Builder()
+ TetherNetwork.Builder builder = new TetherNetwork.Builder()
.setDeviceId(DEVICE_ID)
.setDeviceInfo(DEVICE_INFO)
.setNetworkType(NETWORK_TYPE)
.setNetworkName(NETWORK_NAME)
.setHotspotSsid(HOTSPOT_SSID)
- .setHotspotBssid(HOTSPOT_BSSID)
- .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES);
+ .setHotspotBssid(HOTSPOT_BSSID);
+ Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
+ return builder;
}
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index a04526a..81efa79 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -24,9 +24,8 @@
import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -52,11 +51,10 @@
import java.util.List;
/**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}.
+ * Unit tests for {@link SharedConnectivityService}.
*/
@SmallTest
public class SharedConnectivityServiceTest {
- private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
.setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
.setConnectionStrength(2).setBatteryPercentage(50).build();
@@ -64,12 +62,13 @@
new TetherNetwork.Builder().setDeviceId(1).setDeviceInfo(DEVICE_INFO)
.setNetworkType(NETWORK_TYPE_CELLULAR).setNetworkName("TEST_NETWORK")
.setHotspotSsid("TEST_SSID").setHotspotBssid("TEST_BSSID")
- .setHotspotSecurityTypes(SECURITY_TYPES).build();
+ .addHotspotSecurityType(SECURITY_TYPE_WEP)
+ .addHotspotSecurityType(SECURITY_TYPE_EAP).build();
private static final List<TetherNetwork> TETHER_NETWORKS = List.of(TETHER_NETWORK);
private static final KnownNetwork KNOWN_NETWORK =
new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE_NEARBY_SELF)
- .setSsid("TEST_SSID").setSecurityTypes(SECURITY_TYPES)
- .setDeviceInfo(DEVICE_INFO).build();
+ .setSsid("TEST_SSID").addSecurityType(SECURITY_TYPE_WEP)
+ .addSecurityType(SECURITY_TYPE_EAP).setDeviceInfo(DEVICE_INFO).build();
private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
private static final SharedConnectivitySettingsState SETTINGS_STATE =
new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
@@ -111,7 +110,8 @@
@Test
public void onBind_isNotNull() {
SharedConnectivityService service = createService();
- assertNotNull(service.onBind(new Intent()));
+
+ assertThat(service.onBind(new Intent())).isNotNull();
}
@Test
@@ -121,7 +121,9 @@
(ISharedConnectivityService.Stub) service.onBind(new Intent());
service.setTetherNetworks(TETHER_NETWORKS);
- assertArrayEquals(TETHER_NETWORKS.toArray(), binder.getTetherNetworks().toArray());
+
+ assertThat(binder.getTetherNetworks())
+ .containsExactlyElementsIn(List.copyOf(TETHER_NETWORKS));
}
@Test
@@ -131,7 +133,9 @@
(ISharedConnectivityService.Stub) service.onBind(new Intent());
service.setKnownNetworks(KNOWN_NETWORKS);
- assertArrayEquals(KNOWN_NETWORKS.toArray(), binder.getKnownNetworks().toArray());
+
+ assertThat(binder.getKnownNetworks())
+ .containsExactlyElementsIn(List.copyOf(KNOWN_NETWORKS));
}
@Test
@@ -141,7 +145,8 @@
(ISharedConnectivityService.Stub) service.onBind(new Intent());
service.setSettingsState(SETTINGS_STATE);
- assertEquals(SETTINGS_STATE, binder.getSettingsState());
+
+ assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE);
}
@Test
@@ -151,7 +156,9 @@
(ISharedConnectivityService.Stub) service.onBind(new Intent());
service.updateTetherNetworkConnectionStatus(TETHER_NETWORK_CONNECTION_STATUS);
- assertEquals(TETHER_NETWORK_CONNECTION_STATUS, binder.getTetherNetworkConnectionStatus());
+
+ assertThat(binder.getTetherNetworkConnectionStatus())
+ .isEqualTo(TETHER_NETWORK_CONNECTION_STATUS);
}
@Test
@@ -161,7 +168,9 @@
(ISharedConnectivityService.Stub) service.onBind(new Intent());
service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
- assertEquals(KNOWN_NETWORK_CONNECTION_STATUS, binder.getKnownNetworkConnectionStatus());
+
+ assertThat(binder.getKnownNetworkConnectionStatus())
+ .isEqualTo(KNOWN_NETWORK_CONNECTION_STATUS);
}
private SharedConnectivityService createService() {