diff options
123 files changed, 2151 insertions, 707 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 74033f3fbf09..89eb1a9a7650 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -533,7 +533,8 @@ class JobConcurrencyManager { mIdleContexts.add( mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, batteryStats, - mService.mJobPackageTracker, mContext.getMainLooper())); + mService.mJobPackageTracker, + AppSchedulingModuleThread.get().getLooper())); } } @@ -1925,7 +1926,7 @@ class JobConcurrencyManager { return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)), - mService.mJobPackageTracker, mContext.getMainLooper()); + mService.mJobPackageTracker, AppSchedulingModuleThread.get().getLooper()); } @GuardedBy("mLock") diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index f83eeba88bfb..3cc67e7b5677 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2009,7 +2009,7 @@ public class JobSchedulerService extends com.android.server.SystemService mActivityManagerInternal = Objects.requireNonNull( LocalServices.getService(ActivityManagerInternal.class)); - mHandler = new JobHandler(context.getMainLooper()); + mHandler = new JobHandler(AppSchedulingModuleThread.get().getLooper()); mConstants = new Constants(); mConstantsObserver = new ConstantsObserver(); mJobSchedulerStub = new JobSchedulerStub(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index f0d019e307b0..e55bda7fab02 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -256,7 +256,7 @@ public final class ConnectivityController extends RestrictingController implemen public ConnectivityController(JobSchedulerService service, @NonNull FlexibilityController flexibilityController) { super(service); - mHandler = new CcHandler(mContext.getMainLooper()); + mHandler = new CcHandler(AppSchedulingModuleThread.get().getLooper()); mConnManager = mContext.getSystemService(ConnectivityManager.class); mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java index 847a1bfe4465..122fe695c70b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java @@ -33,6 +33,7 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import com.android.server.AppSchedulingModuleThread; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; import com.android.server.job.StateControllerProto.ContentObserverController.Observer.TriggerContentData; @@ -70,7 +71,7 @@ public final class ContentObserverController extends StateController { public ContentObserverController(JobSchedulerService service) { super(service); - mHandler = new Handler(mContext.getMainLooper()); + mHandler = new Handler(AppSchedulingModuleThread.get().getLooper()); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index bdf72b64d3e0..d5c9ae615486 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -36,6 +36,7 @@ import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; import com.android.internal.util.ArrayUtils; +import com.android.server.AppSchedulingModuleThread; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; @@ -127,7 +128,7 @@ public final class DeviceIdleJobsController extends StateController { public DeviceIdleJobsController(JobSchedulerService service) { super(service); - mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper()); + mHandler = new DeviceIdleJobsDelayHandler(AppSchedulingModuleThread.get().getLooper()); // Register for device idle mode changes mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mLocalDeviceIdleController = diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index c065f2cfba52..2b7438c862bd 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -130,7 +130,7 @@ public class PrefetchController extends StateController { public PrefetchController(JobSchedulerService service) { super(service); mPcConstants = new PcConstants(); - mHandler = new PcHandler(mContext.getMainLooper()); + mHandler = new PcHandler(AppSchedulingModuleThread.get().getLooper()); mThresholdAlarmListener = new ThresholdAlarmListener( mContext, AppSchedulingModuleThread.get().getLooper()); mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 90a29d80e86d..aca0a6e9b18c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -560,13 +560,14 @@ public final class QuotaController extends StateController { @NonNull BackgroundJobsController backgroundJobsController, @NonNull ConnectivityController connectivityController) { super(service); - mHandler = new QcHandler(mContext.getMainLooper()); + mHandler = new QcHandler(AppSchedulingModuleThread.get().getLooper()); mAlarmManager = mContext.getSystemService(AlarmManager.class); mQcConstants = new QcConstants(); mBackgroundJobsController = backgroundJobsController; mConnectivityController = connectivityController; mIsEnabled = !mConstants.USE_TARE_POLICY; - mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, mContext.getMainLooper()); + mInQuotaAlarmQueue = + new InQuotaAlarmQueue(mContext, AppSchedulingModuleThread.get().getLooper()); // Set up the app standby bucketing tracker AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); diff --git a/core/api/current.txt b/core/api/current.txt index 5b9970b6c15d..a53de83c882d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -26782,9 +26782,7 @@ package android.media.tv { } public final class TableResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable { - ctor public TableResponse(int, int, int, @Nullable android.net.Uri, int, int); - ctor public TableResponse(int, int, int, @NonNull byte[], int, int); - ctor public TableResponse(int, int, int, @NonNull android.os.SharedMemory, int, int); + ctor @Deprecated public TableResponse(int, int, int, @Nullable android.net.Uri, int, int); method public int getSize(); method @Nullable public byte[] getTableByteArray(); method @Nullable public android.os.SharedMemory getTableSharedMemory(); @@ -26793,6 +26791,14 @@ package android.media.tv { field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TableResponse> CREATOR; } + public static final class TableResponse.Builder { + ctor public TableResponse.Builder(int, int, int, int, int); + method @NonNull public android.media.tv.TableResponse build(); + method @NonNull public android.media.tv.TableResponse.Builder setTableByteArray(@NonNull byte[]); + method @NonNull public android.media.tv.TableResponse.Builder setTableSharedMemory(@NonNull android.os.SharedMemory); + method @NonNull public android.media.tv.TableResponse.Builder setTableUri(@NonNull android.net.Uri); + } + public final class TimelineRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable { ctor public TimelineRequest(int, int, int); ctor public TimelineRequest(int, int, int, @NonNull String); @@ -27432,7 +27438,7 @@ package android.media.tv { method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle); method public void notifyVideoAvailable(); method public void notifyVideoUnavailable(int); - method public void onAdBuffer(@NonNull android.media.tv.AdBuffer); + method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer); method public void onAppPrivateCommand(@NonNull String, android.os.Bundle); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); @@ -27501,17 +27507,17 @@ package android.media.tv { method @NonNull public android.net.Uri getChannelUri(); method @NonNull public java.util.List<android.media.tv.TvContentRating> getContentRatings(); method @NonNull public String getDescription(); - method @NonNull public long getEndPaddingMillis(); + method public long getEndPaddingMillis(); method @NonNull public String getName(); method @Nullable public android.net.Uri getProgramUri(); - method @IntRange(from=0xffffffff) @NonNull public long getRecordingDurationMillis(); + method @IntRange(from=0xffffffff) public long getRecordingDurationMillis(); method @NonNull public String getRecordingId(); - method @IntRange(from=0xffffffff) @NonNull public long getRecordingStartTimeMillis(); + method @IntRange(from=0xffffffff) public long getRecordingStartTimeMillis(); method @Nullable public android.net.Uri getRecordingUri(); - method @NonNull public int getRepeatDays(); - method @IntRange(from=0) @NonNull public long getScheduledDurationMillis(); - method @IntRange(from=0) @NonNull public long getScheduledStartTimeMillis(); - method @NonNull public long getStartPaddingMillis(); + method public int getRepeatDays(); + method @IntRange(from=0) public long getScheduledDurationMillis(); + method @IntRange(from=0) public long getScheduledStartTimeMillis(); + method public long getStartPaddingMillis(); method public void setDescription(@NonNull String); method public void setName(@NonNull String); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -27743,7 +27749,7 @@ package android.media.tv.interactive { ctor public TvInteractiveAppService.Session(@NonNull android.content.Context); method public boolean isMediaViewEnabled(); method @CallSuper public void layoutSurface(int, int, int, int); - method @CallSuper public void notifyAdBuffer(@NonNull android.media.tv.AdBuffer); + method @CallSuper public void notifyAdBufferReady(@NonNull android.media.tv.AdBuffer); method @CallSuper public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String); method @CallSuper public void notifySessionStateChanged(int, int); method @CallSuper public final void notifyTeletextAppStateChanged(int); @@ -27817,7 +27823,7 @@ package android.media.tv.interactive { method @CallSuper public void requestTimeShiftMode(); method @CallSuper public void requestTrackInfoList(); method @CallSuper public void requestTvRecordingInfo(@NonNull String); - method @CallSuper public void requestTvRecordingInfoList(@NonNull int); + method @CallSuper public void requestTvRecordingInfoList(int); method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle); method @CallSuper public void sendTimeShiftCommandRequest(@NonNull String, @Nullable android.os.Bundle); method @CallSuper public void setMediaViewEnabled(boolean); @@ -27916,7 +27922,7 @@ package android.media.tv.interactive { method public void onRequestTimeShiftMode(@NonNull String); method public void onRequestTrackInfoList(@NonNull String); method public void onRequestTvRecordingInfo(@NonNull String, @NonNull String); - method public void onRequestTvRecordingInfoList(@NonNull String, @NonNull int); + method public void onRequestTvRecordingInfoList(@NonNull String, int); method public void onSetTvRecordingInfo(@NonNull String, @NonNull String, @NonNull android.media.tv.TvRecordingInfo); method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect); method public void onStateChanged(@NonNull String, int, int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 01b0c7d30831..995602f27d2b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10129,9 +10129,9 @@ package android.net.wifi.sharedconnectivity.app { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean disconnectHotspotNetwork(@NonNull android.net.wifi.sharedconnectivity.app.HotspotNetwork); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus(); - method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.HotspotNetwork> getHotspotNetworks(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.HotspotNetwork> getHotspotNetworks(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus getKnownNetworkConnectionStatus(); - method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState getSettingsState(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback); @@ -10158,6 +10158,8 @@ package android.net.wifi.sharedconnectivity.service { public abstract class SharedConnectivityService extends android.app.Service { ctor public SharedConnectivityService(); + method public static boolean areHotspotNetworksEnabledForService(@NonNull android.content.Context); + method public static boolean areKnownNetworksEnabledForService(@NonNull android.content.Context); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onConnectHotspotNetwork(@NonNull android.net.wifi.sharedconnectivity.app.HotspotNetwork); method public abstract void onConnectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 9640b0e506da..631df014a580 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1132,6 +1132,12 @@ public class CameraMetadataNative implements Parcelable { private boolean setGpsLocation(Location l) { if (l == null) { + // If Location value being set is null, remove corresponding keys. + // This is safe because api1/client2/CameraParameters.cpp already erases + // the keys for JPEG_GPS_LOCATION for certain cases. + setBase(CaptureRequest.JPEG_GPS_TIMESTAMP, null); + setBase(CaptureRequest.JPEG_GPS_COORDINATES, null); + setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, null); return false; } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index cb678b98a998..5a48176d29e7 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -26,7 +26,6 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; -import android.util.Log; import android.util.Range; import android.util.Size; import android.util.SparseIntArray; @@ -2019,7 +2018,7 @@ public final class StreamConfigurationMap { /** * @hide */ - public static final int HAL_DATASPACE_HEIF = 0x1003; + public static final int HAL_DATASPACE_HEIF = 0x1004; /** * @hide */ diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index fb8f84acc8f3..a52e3d49148d 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -474,9 +474,8 @@ public class GraphicsEnvironment { * target functionality. The ANGLE broadcast receiver code will apply a "deferlist" at * the first boot of a newly-flashed device. However, there is a gap in time between * when applications can start and when the deferlist is applied. For now, assume that - * if ANGLE is the system driver and Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS is - * empty, that the deferlist has not yet been applied. In this case, select the Legacy - * driver. + * if ANGLE is the system driver and Settings.Global.ANGLE_DEFERLIST is empty, that the + * deferlist has not yet been applied. In this case, select the Legacy driver. * otherwise ... * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ... * 4) The global switch (i.e. use the system driver, whether ANGLE or legacy; @@ -516,14 +515,17 @@ public class GraphicsEnvironment { contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS); final List<String> optInValues = getGlobalSettingsString( contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES); + final List<String> angleDeferlist = getGlobalSettingsString( + contentResolver, bundle, Settings.Global.ANGLE_DEFERLIST); Log.v(TAG, "Currently set values for:"); Log.v(TAG, " angle_gl_driver_selection_pkgs =" + optInPackages); Log.v(TAG, " angle_gl_driver_selection_values =" + optInValues); // If ANGLE is the system driver AND the deferlist has not yet been applied, select the // Legacy driver - if (mAngleIsSystemDriver && optInPackages.size() <= 1) { - Log.v(TAG, "Ignoring angle_gl_driver_selection_* until deferlist has been applied"); + if (mAngleIsSystemDriver && angleDeferlist.size() == 0) { + Log.v(TAG, "ANGLE deferlist (" + Settings.Global.ANGLE_DEFERLIST + ") has not been " + + "applied, defaulting to legacy driver"); return ANGLE_GL_DRIVER_TO_USE_LEGACY; } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 0ef8bb64acaf..e81ca1ada98a 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -28,6 +28,7 @@ import android.content.ClipData; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -434,14 +435,14 @@ public final class Dataset implements Parcelable { * one value for a field or set an authentication intent. */ public static final class Builder { - private ArrayList<AutofillId> mFieldIds; - private ArrayList<AutofillValue> mFieldValues; - private ArrayList<RemoteViews> mFieldPresentations; - private ArrayList<RemoteViews> mFieldDialogPresentations; - private ArrayList<InlinePresentation> mFieldInlinePresentations; - private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations; - private ArrayList<DatasetFieldFilter> mFieldFilters; - private ArrayList<String> mAutofillDatatypes; + private ArrayList<AutofillId> mFieldIds = new ArrayList<>(); + private ArrayList<AutofillValue> mFieldValues = new ArrayList(); + private ArrayList<RemoteViews> mFieldPresentations = new ArrayList(); + private ArrayList<RemoteViews> mFieldDialogPresentations = new ArrayList(); + private ArrayList<InlinePresentation> mFieldInlinePresentations = new ArrayList(); + private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations = new ArrayList(); + private ArrayList<DatasetFieldFilter> mFieldFilters = new ArrayList(); + private ArrayList<String> mAutofillDatatypes = new ArrayList(); @Nullable private ClipData mFieldContent; private RemoteViews mPresentation; private RemoteViews mDialogPresentation; @@ -452,6 +453,15 @@ public final class Dataset implements Parcelable { @Nullable private String mId; /** + * Usually, a field will be associated with a single autofill id and/or datatype. + * There could be null field value corresponding to different autofill ids or datatye + * values, but the implementation is ok with duplicating that information. + * This map is just for the purpose of optimization, to reduce the size of the pelled data + * over the binder transaction. + */ + private ArrayMap<Field, Integer> mFieldToIndexdMap = new ArrayMap<>(); + + /** * Creates a new builder. * * @param presentation The presentation used to visualize this dataset. @@ -1051,29 +1061,40 @@ public final class Dataset implements Parcelable { */ public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) { throwIfDestroyed(); + + if (mFieldToIndexdMap.containsKey(field)) { + int index = mFieldToIndexdMap.get(field); + if (mFieldIds.get(index) == null) { + mFieldIds.set(index, id); + return this; + } + // if the Autofill Id is already set, ignore and proceed as if setting in a new + // value. + } + int index; if (field == null) { - setLifeTheUniverseAndEverything(id, null, null, null, null, null, null); + index = setLifeTheUniverseAndEverything(id, null, null, null, null, null, null); } else { final DatasetFieldFilter filter = field.getDatasetFieldFilter(); final Presentations presentations = field.getPresentations(); if (presentations == null) { - setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null, + index = setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null, filter, null); } else { - setLifeTheUniverseAndEverything(id, field.getValue(), + index = setLifeTheUniverseAndEverything(id, field.getValue(), presentations.getMenuPresentation(), presentations.getInlinePresentation(), presentations.getInlineTooltipPresentation(), filter, presentations.getDialogPresentation()); } } + mFieldToIndexdMap.put(field, index); return this; } /** - * Adds a field to this Dataset with a specific type and no - * AutofillId. This is used to send back Field information - * when Autofilling with platform detections is on. + * Adds a field to this Dataset with a specific type. This is used to send back Field + * information when Autofilling with platform detections is on. * Platform detections are on when receiving a populated list from * FillRequest#getHints(). * @@ -1086,9 +1107,6 @@ public final class Dataset implements Parcelable { * has two credential pairs, then two Datasets should be created, * and so on. * - * Using this will remove any data populated with - * setField(@NonNull AutofillId id, @Nullable Field field). - * * @param hint An autofill hint returned from {@link * FillRequest#getHints()}. * @@ -1102,19 +1120,29 @@ public final class Dataset implements Parcelable { public @NonNull Dataset.Builder setField(@NonNull String hint, @NonNull Field field) { throwIfDestroyed(); + if (mFieldToIndexdMap.containsKey(field)) { + int index = mFieldToIndexdMap.get(field); + if (mAutofillDatatypes.get(index) == null) { + mAutofillDatatypes.set(index, hint); + return this; + } + // if the hint is already set, ignore and proceed as if setting in a new hint. + } + + int index; final DatasetFieldFilter filter = field.getDatasetFieldFilter(); final Presentations presentations = field.getPresentations(); if (presentations == null) { - setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null, + index = setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null, filter, null); } else { - setLifeTheUniverseAndEverything(hint, field.getValue(), + index = setLifeTheUniverseAndEverything(hint, field.getValue(), presentations.getMenuPresentation(), presentations.getInlinePresentation(), presentations.getInlineTooltipPresentation(), filter, presentations.getDialogPresentation()); } - + mFieldToIndexdMap.put(field, index); return this; } @@ -1172,67 +1200,64 @@ public final class Dataset implements Parcelable { return this; } - private void setLifeTheUniverseAndEverything(String datatype, + /** Returns the index at which this id was modified or inserted */ + private int setLifeTheUniverseAndEverything(@NonNull String datatype, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter, @Nullable RemoteViews dialogPresentation) { - if (mAutofillDatatypes == null) { - mFieldValues = new ArrayList<>(); - mFieldPresentations = new ArrayList<>(); - mFieldDialogPresentations = new ArrayList<>(); - mFieldInlinePresentations = new ArrayList<>(); - mFieldInlineTooltipPresentations = new ArrayList<>(); - mFieldFilters = new ArrayList<>(); - mAutofillDatatypes = new ArrayList<>(); - mFieldIds = null; + Objects.requireNonNull(datatype, "datatype cannot be null"); + final int existingIdx = mAutofillDatatypes.indexOf(datatype); + if (existingIdx >= 0) { + mAutofillDatatypes.add(datatype); + mFieldValues.set(existingIdx, value); + mFieldPresentations.set(existingIdx, presentation); + mFieldDialogPresentations.set(existingIdx, dialogPresentation); + mFieldInlinePresentations.set(existingIdx, inlinePresentation); + mFieldInlineTooltipPresentations.set(existingIdx, tooltip); + mFieldFilters.set(existingIdx, filter); + return existingIdx; } + mFieldIds.add(null); + mAutofillDatatypes.add(datatype); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldDialogPresentations.add(dialogPresentation); mFieldInlinePresentations.add(inlinePresentation); mFieldInlineTooltipPresentations.add(tooltip); mFieldFilters.add(filter); - mAutofillDatatypes.add(datatype); + return mFieldIds.size() - 1; } - private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, + /** Returns the index at which this id was modified or inserted */ + private int setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter, @Nullable RemoteViews dialogPresentation) { Objects.requireNonNull(id, "id cannot be null"); - if (mFieldIds != null) { - final int existingIdx = mFieldIds.indexOf(id); - if (existingIdx >= 0) { - mFieldValues.set(existingIdx, value); - mFieldPresentations.set(existingIdx, presentation); - mFieldDialogPresentations.set(existingIdx, dialogPresentation); - mFieldInlinePresentations.set(existingIdx, inlinePresentation); - mFieldInlineTooltipPresentations.set(existingIdx, tooltip); - mFieldFilters.set(existingIdx, filter); - return; - } - } else { - mFieldIds = new ArrayList<>(); - mFieldValues = new ArrayList<>(); - mFieldPresentations = new ArrayList<>(); - mFieldDialogPresentations = new ArrayList<>(); - mFieldInlinePresentations = new ArrayList<>(); - mFieldInlineTooltipPresentations = new ArrayList<>(); - mFieldFilters = new ArrayList<>(); - mAutofillDatatypes = null; + final int existingIdx = mFieldIds.indexOf(id); + if (existingIdx >= 0) { + mFieldValues.set(existingIdx, value); + mFieldPresentations.set(existingIdx, presentation); + mFieldDialogPresentations.set(existingIdx, dialogPresentation); + mFieldInlinePresentations.set(existingIdx, inlinePresentation); + mFieldInlineTooltipPresentations.set(existingIdx, tooltip); + mFieldFilters.set(existingIdx, filter); + return existingIdx; } mFieldIds.add(id); + mAutofillDatatypes.add(null); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldDialogPresentations.add(dialogPresentation); mFieldInlinePresentations.add(inlinePresentation); mFieldInlineTooltipPresentations.add(tooltip); mFieldFilters.add(filter); + return mFieldIds.size() - 1; } /** @@ -1249,11 +1274,12 @@ public final class Dataset implements Parcelable { throwIfDestroyed(); mDestroyed = true; if (mFieldIds == null && mAutofillDatatypes == null) { - throw new IllegalStateException("at least one value must be set"); + throw new IllegalStateException("at least one of field or datatype must be set"); } if (mFieldIds != null && mAutofillDatatypes != null) { - if (mFieldIds.size() > 0 && mAutofillDatatypes.size() > 0) { - throw new IllegalStateException("both field and datatype were populated"); + if (mFieldIds.size() == 0 && mAutofillDatatypes.size() == 0) { + throw new IllegalStateException( + "at least one of field or datatype must be set"); } } if (mFieldContent != null) { diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java index a3892238f1e6..a2fa1392b079 100644 --- a/core/java/android/service/dreams/DreamActivity.java +++ b/core/java/android/service/dreams/DreamActivity.java @@ -58,13 +58,11 @@ public class DreamActivity extends Activity { setTitle(title); } - final Object callback = getIntent().getExtras().getBinder(EXTRA_CALLBACK); - if (callback instanceof DreamService.DreamActivityCallbacks) { - mCallback = (DreamService.DreamActivityCallbacks) callback; + final Bundle extras = getIntent().getExtras(); + mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK); + + if (mCallback != null) { mCallback.onActivityCreated(this); - } else { - mCallback = null; - finishAndRemoveTask(); } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index c7099fdd202a..ce8af83a58f8 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1409,6 +1409,10 @@ public class DreamService extends Service implements Window.Callback { // Request the DreamOverlay be told to dream with dream's window // parameters once the window has been attached. mDreamStartOverlayConsumer = overlay -> { + if (mWindow == null) { + Slog.d(TAG, "mWindow is null"); + return; + } try { overlay.startDream(mWindow.getAttributes(), mOverlayCallback, mDreamComponent.flattenToString(), diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8bf32324dd5a..ef76ce36ec0f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -142,7 +142,6 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import android.view.autofill.AutofillFeatureFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -10363,15 +10362,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean isAutofillable() { if (getAutofillType() == AUTOFILL_TYPE_NONE) return false; + final AutofillManager afm = getAutofillManager(); + if (afm == null) { + return false; + } + // Disable triggering autofill if the view is integrated with CredentialManager. - if (AutofillFeatureFlags.shouldIgnoreCredentialViews() - && isCredential()) return false; + if (afm.shouldIgnoreCredentialViews() && isCredential()) { + return false; + } if (!isImportantForAutofill()) { // If view matches heuristics and is not denied, it will be treated same as view that's // important for autofill - if (isMatchingAutofillableHeuristics() - && !isActivityDeniedForAutofillForUnimportantView()) { + if (afm.isMatchingAutofillableHeuristics(this) + && !afm.isActivityDeniedForAutofillForUnimportantView()) { return getAutofillViewId() > LAST_APP_AUTOFILL_ID; } // View is not important for "regular" autofill, so we must check if Augmented Autofill @@ -10380,8 +10385,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (options == null || !options.isAugmentedAutofillEnabled(mContext)) { return false; } - final AutofillManager afm = getAutofillManager(); - if (afm == null) return false; + afm.notifyViewEnteredForAugmentedAutofill(this); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a8b4da14c1c5..24dcb69f8342 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5718,7 +5718,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 34; private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 35; private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36; - + private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37; final class ViewRootHandler extends Handler { @Override @@ -6006,6 +6006,11 @@ public final class ViewRootImpl implements ViewParent, case MSG_REQUEST_SCROLL_CAPTURE: handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj); break; + case MSG_PAUSED_FOR_SYNC_TIMEOUT: + Log.e(mTag, "Timedout waiting to unpause for sync"); + mNumPausedForSync = 0; + scheduleTraversals(); + break; } } } @@ -11584,9 +11589,16 @@ public final class ViewRootImpl implements ViewParent, mActiveSurfaceSyncGroup = new SurfaceSyncGroup(mTag); mActiveSurfaceSyncGroup.setAddedToSyncListener(() -> { Runnable runnable = () -> { - mNumPausedForSync--; - if (!mIsInTraversal && mNumPausedForSync == 0) { - scheduleTraversals(); + // Check if it's already 0 because the timeout could have reset the count to + // 0 and we don't want to go negative. + if (mNumPausedForSync > 0) { + mNumPausedForSync--; + } + if (mNumPausedForSync == 0) { + mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); + if (!mIsInTraversal) { + scheduleTraversals(); + } } }; @@ -11613,6 +11625,8 @@ public final class ViewRootImpl implements ViewParent, } mNumPausedForSync++; + mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, 1000); return mActiveSurfaceSyncGroup; }; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index ab9f492694f5..14c781bb5560 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -691,6 +691,9 @@ public final class AutofillManager { // Indicates whether called the showAutofillDialog() method. private boolean mShowAutofillDialogCalled = false; + // Cached autofill feature flag + private boolean mShouldIgnoreCredentialViews = false; + private final String[] mFillDialogEnabledHints; // Tracked all views that have appeared, including views that there are no @@ -838,6 +841,7 @@ public final class AutofillManager { mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled(); mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints(); + mShouldIgnoreCredentialViews = AutofillFeatureFlags.shouldIgnoreCredentialViews(); if (sDebug) { Log.d(TAG, "Fill dialog is enabled:" + mIsFillDialogEnabled + ", hints=" + Arrays.toString(mFillDialogEnabledHints)); @@ -2081,6 +2085,11 @@ public final class AutofillManager { } /** @hide */ + public boolean shouldIgnoreCredentialViews() { + return mShouldIgnoreCredentialViews; + } + + /** @hide */ public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { if (!hasAutofillFeature()) { return; diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 78de9544b59b..0672d6318212 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -22,6 +22,8 @@ import android.annotation.UiThread; import android.os.Binder; import android.os.BinderProxy; import android.os.Debug; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; @@ -60,6 +62,7 @@ public final class SurfaceSyncGroup { private static final int MAX_COUNT = 100; private static final AtomicInteger sCounter = new AtomicInteger(0); + private static final int TRANSACTION_READY_TIMEOUT = 1000; private static Supplier<Transaction> sTransactionFactory = Transaction::new; @@ -111,6 +114,13 @@ public final class SurfaceSyncGroup { */ private final Binder mToken = new Binder(); + private static final Object sHandlerThreadLock = new Object(); + @GuardedBy("sHandlerThreadLock") + private static HandlerThread sHandlerThread; + private Handler mHandler; + + private boolean mTimeoutAdded; + private static boolean isLocalBinder(IBinder binder) { return !(binder instanceof BinderProxy); } @@ -538,6 +548,11 @@ public final class SurfaceSyncGroup { + transactionReadyCallback.hashCode()); } + // Start the timeout when this SurfaceSyncGroup has been added to a parent SurfaceSyncGroup. + // This is because if the other SurfaceSyncGroup has bugs and doesn't complete, this SSG + // will get stuck. It's better to complete this SSG even if the parent SSG is broken. + addTimeout(); + boolean finished = false; Runnable addedToSyncListener = null; synchronized (mLock) { @@ -641,6 +656,9 @@ public final class SurfaceSyncGroup { } mTransactionReadyConsumer.accept(mTransaction); mFinished = true; + if (mTimeoutAdded) { + mHandler.removeCallbacksAndMessages(this); + } } /** @@ -701,6 +719,12 @@ public final class SurfaceSyncGroup { } } + // Start the timeout when another SSG has been added to this SurfaceSyncGroup. This is + // because if the other SurfaceSyncGroup has bugs and doesn't complete, it will affect this + // SSGs. So it's better to just add a timeout in case the other SSG doesn't invoke the + // callback and complete this SSG. + addTimeout(); + return transactionReadyCallback; } @@ -731,6 +755,41 @@ public final class SurfaceSyncGroup { } } + private void addTimeout() { + synchronized (sHandlerThreadLock) { + if (sHandlerThread == null) { + sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer"); + sHandlerThread.start(); + } + } + + synchronized (mLock) { + if (mTimeoutAdded) { + // We only need one timeout for the entire SurfaceSyncGroup since we just want to + // ensure it doesn't stay stuck forever. + return; + } + + if (mHandler == null) { + mHandler = new Handler(sHandlerThread.getLooper()); + } + + mTimeoutAdded = true; + } + + Runnable runnable = () -> { + Log.e(TAG, "Failed to receive transaction ready in " + TRANSACTION_READY_TIMEOUT + + "ms. Marking SurfaceSyncGroup as ready " + mName); + // Clear out any pending syncs in case the other syncs can't complete or timeout due to + // a crash. + synchronized (mLock) { + mPendingSyncs.clear(); + } + markSyncReady(); + }; + mHandler.postDelayed(runnable, this, TRANSACTION_READY_TIMEOUT); + } + /** * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 16511a678760..27c477f3f241 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3020,10 +3020,18 @@ >com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string> <!-- Package name of the system service that implements the shared connectivity service --> - <string translatable="false" name="shared_connectivity_service_package"></string> + <string translatable="false" name="config_sharedConnectivityServicePackage"></string> <!-- Intent action used when binding to the shared connectivity service --> - <string translatable="false" name="shared_connectivity_service_intent_action"></string> + <string translatable="false" name="config_sharedConnectivityServiceIntentAction"></string> + + <!-- The system and settings UI can support all the features of instant tether. If set to false, + instant tether will run in notifications mode --> + <bool name="config_hotspotNetworksEnabledForService">false</bool> + + <!-- The system and settings UI can support all the features of known networks. If set to false, + known networks will run in notifications mode --> + <bool name="config_knownNetworksEnabledForService">false</bool> <!-- Component name of the activity that shows the usb containment status. --> <string name="config_usbContaminantActivity" translatable="false" diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c34d31cca7ba..a15833d36870 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4960,8 +4960,10 @@ <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/> <java-symbol type="string" name="config_wearServiceComponent" /> - <java-symbol type="string" name="shared_connectivity_service_package" /> - <java-symbol type="string" name="shared_connectivity_service_intent_action" /> + <java-symbol type="string" name="config_sharedConnectivityServicePackage" /> + <java-symbol type="string" name="config_sharedConnectivityServiceIntentAction" /> + <java-symbol type="bool" name="config_hotspotNetworksEnabledForService"/> + <java-symbol type="bool" name="config_knownNetworksEnabledForService"/> <!-- Whether to show weather on the lockscreen by default. --> <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" /> diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java index ccf8085f87ff..b6fc137471a4 100644 --- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java +++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java @@ -91,7 +91,7 @@ public class NameValueCacheTest { mConfigsCacheGenerationStore = new MemoryIntArray(2); mConfigsCacheGenerationStore.set(0, 123); mConfigsCacheGenerationStore.set(1, 456); - mSettingsCacheGenerationStore = new MemoryIntArray(3); + mSettingsCacheGenerationStore = new MemoryIntArray(2); mSettingsCacheGenerationStore.set(0, 234); mSettingsCacheGenerationStore.set(1, 567); mConfigsStorage = new HashMap<>(); @@ -163,10 +163,6 @@ public class NameValueCacheTest { Bundle incomingBundle = invocationOnMock.getArgument(4); String key = invocationOnMock.getArgument(3); String value = incomingBundle.getString(Settings.NameValueTable.VALUE); - boolean newSetting = false; - if (!mSettingsStorage.containsKey(key)) { - newSetting = true; - } mSettingsStorage.put(key, value); int currentGeneration; // Different settings have different generation codes @@ -177,18 +173,12 @@ public class NameValueCacheTest { currentGeneration = mSettingsCacheGenerationStore.get(1); mSettingsCacheGenerationStore.set(1, ++currentGeneration); } - if (newSetting) { - // Tracking the generation of all unset settings. - // Increment when a new setting is inserted - currentGeneration = mSettingsCacheGenerationStore.get(2); - mSettingsCacheGenerationStore.set(2, ++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 caller asked for the generation key. + // 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 -> { @@ -199,15 +189,9 @@ public class NameValueCacheTest { Bundle bundle = new Bundle(); bundle.putSerializable(Settings.NameValueTable.VALUE, value); - if (incomingBundle.containsKey( + if (value != null && incomingBundle.containsKey( Settings.CALL_METHOD_TRACK_GENERATION_KEY)) { - int index; - if (value != null) { - index = key.equals(SETTING) ? 0 : 1; - } else { - // special index for unset settings - index = 2; - } + 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); @@ -377,38 +361,16 @@ public class NameValueCacheTest { } @Test - public void testCaching_unsetSetting() throws Exception { + 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); - // The first unset setting's generation number is cached - verifyNoMoreInteractions(mMockIContentProvider); - assertThat(cachedValue).isNull(); - - String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2); + // Empty list won't be cached verify(mMockIContentProvider, times(2)).call(any(), any(), eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); - assertThat(returnedValue2).isNull(); - - String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING); - // The second unset setting's generation number is cached - verifyNoMoreInteractions(mMockIContentProvider); - assertThat(cachedValue2).isNull(); - - Settings.Secure.putString(mMockContentResolver, SETTING, "a"); - // The generation for unset settings should have changed - returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2); - verify(mMockIContentProvider, times(3)).call(any(), any(), - eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); - assertThat(returnedValue2).isNull(); - - // The generation tracker for the first setting should have change because it's set now - returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING); - verify(mMockIContentProvider, times(4)).call(any(), any(), - eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); - assertThat(returnedValue).isEqualTo("a"); + assertThat(cachedValue).isNull(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 852fae695046..ef53839cb2ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -139,6 +139,30 @@ public class BubbleController implements ConfigurationChangeListener { private static final boolean BUBBLE_BAR_ENABLED = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); + + /** + * Common interface to send updates to bubble views. + */ + public interface BubbleViewCallback { + /** Called when the provided bubble should be removed. */ + void removeBubble(Bubble removedBubble); + /** Called when the provided bubble should be added. */ + void addBubble(Bubble addedBubble); + /** Called when the provided bubble should be updated. */ + void updateBubble(Bubble updatedBubble); + /** Called when the provided bubble should be selected. */ + void selectionChanged(BubbleViewProvider selectedBubble); + /** Called when the provided bubble's suppression state has changed. */ + void suppressionChanged(Bubble bubble, boolean isSuppressed); + /** Called when the expansion state of bubbles has changed. */ + void expansionChanged(boolean isExpanded); + /** + * Called when the order of the bubble list has changed. Depending on the expanded state + * the pointer might need to be updated. + */ + void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer); + } + private final Context mContext; private final BubblesImpl mImpl = new BubblesImpl(); private Bubbles.BubbleExpandListener mExpandListener; @@ -162,7 +186,6 @@ public class BubbleController implements ConfigurationChangeListener { // Used to post to main UI thread private final ShellExecutor mMainExecutor; private final Handler mMainHandler; - private final ShellExecutor mBackgroundExecutor; private BubbleLogger mLogger; @@ -1320,6 +1343,58 @@ public class BubbleController implements ConfigurationChangeListener { }); } + private final BubbleViewCallback mBubbleViewCallback = new BubbleViewCallback() { + @Override + public void removeBubble(Bubble removedBubble) { + if (mStackView != null) { + mStackView.removeBubble(removedBubble); + } + } + + @Override + public void addBubble(Bubble addedBubble) { + if (mStackView != null) { + mStackView.addBubble(addedBubble); + } + } + + @Override + public void updateBubble(Bubble updatedBubble) { + if (mStackView != null) { + mStackView.updateBubble(updatedBubble); + } + } + + @Override + public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) { + if (mStackView != null) { + mStackView.updateBubbleOrder(bubbleOrder, updatePointer); + } + } + + @Override + public void suppressionChanged(Bubble bubble, boolean isSuppressed) { + if (mStackView != null) { + mStackView.setBubbleSuppressed(bubble, isSuppressed); + } + } + + @Override + public void expansionChanged(boolean isExpanded) { + if (mStackView != null) { + mStackView.setExpanded(isExpanded); + } + } + + @Override + public void selectionChanged(BubbleViewProvider selectedBubble) { + if (mStackView != null) { + mStackView.setSelectedBubble(selectedBubble); + } + + } + }; + @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @@ -1342,7 +1417,8 @@ public class BubbleController implements ConfigurationChangeListener { // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); - mStackView.updateOverflowButtonDot(); + // If bubbles in the overflow have a dot, make sure the overflow shows a dot + updateOverflowButtonDot(); // Update bubbles in overflow. if (mOverflowListener != null) { @@ -1357,9 +1433,7 @@ public class BubbleController implements ConfigurationChangeListener { final Bubble bubble = removed.first; @Bubbles.DismissReason final int reason = removed.second; - if (mStackView != null) { - mStackView.removeBubble(bubble); - } + mBubbleViewCallback.removeBubble(bubble); // Leave the notification in place if we're dismissing due to user switching, or // because DND is suppressing the bubble. In both of those cases, we need to be able @@ -1389,49 +1463,47 @@ public class BubbleController implements ConfigurationChangeListener { } mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); - if (update.addedBubble != null && mStackView != null) { + if (update.addedBubble != null) { mDataRepository.addBubble(mCurrentUserId, update.addedBubble); - mStackView.addBubble(update.addedBubble); + mBubbleViewCallback.addBubble(update.addedBubble); } - if (update.updatedBubble != null && mStackView != null) { - mStackView.updateBubble(update.updatedBubble); + if (update.updatedBubble != null) { + mBubbleViewCallback.updateBubble(update.updatedBubble); } - if (update.suppressedBubble != null && mStackView != null) { - mStackView.setBubbleSuppressed(update.suppressedBubble, true); + if (update.suppressedBubble != null) { + mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true); } - if (update.unsuppressedBubble != null && mStackView != null) { - mStackView.setBubbleSuppressed(update.unsuppressedBubble, false); + if (update.unsuppressedBubble != null) { + mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false); } boolean collapseStack = update.expandedChanged && !update.expanded; // At this point, the correct bubbles are inflated in the stack. // Make sure the order in bubble data is reflected in bubble row. - if (update.orderChanged && mStackView != null) { + if (update.orderChanged) { mDataRepository.addBubbles(mCurrentUserId, update.bubbles); // if the stack is going to be collapsed, do not update pointer position // after reordering - mStackView.updateBubbleOrder(update.bubbles, !collapseStack); + mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack); } if (collapseStack) { - mStackView.setExpanded(false); + mBubbleViewCallback.expansionChanged(/* expanded= */ false); mSysuiProxy.requestNotificationShadeTopUi(false, TAG); } - if (update.selectionChanged && mStackView != null) { - mStackView.setSelectedBubble(update.selectedBubble); + if (update.selectionChanged) { + mBubbleViewCallback.selectionChanged(update.selectedBubble); } // Expanding? Apply this last. if (update.expandedChanged && update.expanded) { - if (mStackView != null) { - mStackView.setExpanded(true); - mSysuiProxy.requestNotificationShadeTopUi(true, TAG); - } + mBubbleViewCallback.expansionChanged(/* expanded= */ true); + mSysuiProxy.requestNotificationShadeTopUi(true, TAG); } mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate"); @@ -1442,6 +1514,19 @@ public class BubbleController implements ConfigurationChangeListener { } }; + private void updateOverflowButtonDot() { + BubbleOverflow overflow = mBubbleData.getOverflow(); + if (overflow == null) return; + + for (Bubble b : mBubbleData.getOverflowBubbles()) { + if (b.showDot()) { + overflow.setShowDot(true); + return; + } + } + overflow.setShowDot(false); + } + private boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback) { if (isSummaryOfBubbles(entry)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 7d71089ef4fa..0b947c8b9b08 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1359,16 +1359,6 @@ public class BubbleStackView extends FrameLayout updateOverflowVisibility(); } - void updateOverflowButtonDot() { - for (Bubble b : mBubbleData.getOverflowBubbles()) { - if (b.showDot()) { - mBubbleOverflow.setShowDot(true); - return; - } - } - mBubbleOverflow.setShowDot(false); - } - /** * Handle theme changes. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 9796e4c29352..2d84d211e30a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -267,6 +267,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { mLaunchRootTask = taskInfo; } + if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId + && !taskInfo.equals(mHomeTask)) { + mHomeTask = taskInfo; + } + super.onTaskInfoChanged(taskInfo); } @@ -376,6 +381,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { final WindowContainerTransaction wct = getWindowContainerTransaction(); final Rect taskBounds = calculateBounds(); wct.setBounds(mLaunchRootTask.token, taskBounds); + wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight)); mSyncQueue.queue(wct); final SurfaceControl finalLeash = mLaunchRootLeash; mSyncQueue.runInSync(t -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl index 2624ee536b58..d961d8658b98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl @@ -70,4 +70,9 @@ interface IPip { * Sets the next pip animation type to be the alpha animation. */ oneway void setPipAnimationTypeToAlpha() = 5; + + /** + * Sets the height and visibility of the Launcher keep clear area. + */ + oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index e9d257139779..eb336d56b62c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -1179,6 +1179,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** + * Directly update the animator bounds. + */ + public void updateAnimatorBounds(Rect bounds) { + final PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.isRunning()) { + if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) { + animator.updateEndValue(bounds); + } + animator.setDestinationBounds(bounds); + } + } + + /** * Handles all changes to the PictureInPictureParams. */ protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java index c6b5ce93fd35..db6138a0891f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java @@ -93,6 +93,11 @@ public class PipTransitionState { return hasEnteredPip(mState); } + /** Returns true if activity is currently entering PiP mode. */ + public boolean isEnteringPip() { + return isEnteringPip(mState); + } + public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) { mInSwipePipToHomeTransition = inSwipePipToHomeTransition; } @@ -130,6 +135,11 @@ public class PipTransitionState { return state == ENTERED_PIP; } + /** Returns true if activity is currently entering PiP mode. */ + public static boolean isEnteringPip(@TransitionState int state) { + return state == ENTERING_PIP; + } + public interface OnPipTransitionStateChangedListener { void onPipTransitionStateChanged(@TransitionState int oldState, @TransitionState int newState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index a1483a8dedae..99cd00a67cf7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -101,6 +101,7 @@ import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -181,14 +182,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb // early bail out if the keep clear areas feature is disabled return; } - // only move if already in pip, other transitions account for keep clear areas - if (mPipTransitionState.hasEnteredPip()) { + // only move if we're in PiP or transitioning into PiP + if (!mPipTransitionState.shouldBlockResizeRequest()) { Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState, mPipBoundsAlgorithm); // only move if the bounds are actually different if (destBounds != mPipBoundsState.getBounds()) { - mPipTaskOrganizer.scheduleAnimateResizePip(destBounds, - mEnterAnimationDuration, null); + if (mPipTransitionState.hasEnteredPip()) { + // if already in PiP, schedule separate animation + mPipTaskOrganizer.scheduleAnimateResizePip(destBounds, + mEnterAnimationDuration, null); + } else if (mPipTransitionState.isEnteringPip()) { + // while entering PiP we just need to update animator bounds + mPipTaskOrganizer.updateAnimatorBounds(destBounds); + } } } } @@ -874,6 +881,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb } } + private void setLauncherKeepClearAreaHeight(boolean visible, int height) { + if (visible) { + Rect rect = new Rect( + 0, mPipBoundsState.getDisplayBounds().bottom - height, + mPipBoundsState.getDisplayBounds().right, + mPipBoundsState.getDisplayBounds().bottom); + Set<Rect> restrictedKeepClearAreas = new HashSet<>( + mPipBoundsState.getRestrictedKeepClearAreas()); + restrictedKeepClearAreas.add(rect); + mPipBoundsState.setKeepClearAreas(restrictedKeepClearAreas, + mPipBoundsState.getUnrestrictedKeepClearAreas()); + updatePipPositionForKeepClearAreas(); + } + } + private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { mOnIsInPipStateChangedListener = callback; if (mOnIsInPipStateChangedListener != null) { @@ -1240,6 +1262,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override + public void setLauncherKeepClearAreaHeight(boolean visible, int height) { + executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight", + (controller) -> { + controller.setLauncherKeepClearAreaHeight(visible, height); + }); + } + + @Override public void setPipAnimationListener(IPipAnimationListener listener) { executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener", (controller) -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java index a3d364a0068e..0bce3acecb3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -40,6 +40,7 @@ class TaskPositioner implements DragPositioningCallback { private final DisplayController mDisplayController; private final WindowDecoration mWindowDecoration; + private final Rect mTempBounds = new Rect(); private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); @@ -117,17 +118,32 @@ class TaskPositioner implements DragPositioningCallback { final float deltaX = x - mRepositionStartPoint.x; final float deltaY = y - mRepositionStartPoint.y; mRepositionTaskBounds.set(mTaskBoundsAtDragStart); + + final Rect stableBounds = mTempBounds; + // Make sure the new resizing destination in any direction falls within the stable bounds. + // If not, set the bounds back to the old location that was valid to avoid conflicts with + // some regions such as the gesture area. + mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId()) + .getStableBounds(stableBounds); if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { - mRepositionTaskBounds.left += deltaX; + final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX; + mRepositionTaskBounds.left = (candidateLeft > stableBounds.left) + ? candidateLeft : oldLeft; } if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { - mRepositionTaskBounds.right += deltaX; + final int candidateRight = mRepositionTaskBounds.right + (int) deltaX; + mRepositionTaskBounds.right = (candidateRight < stableBounds.right) + ? candidateRight : oldRight; } if ((mCtrlType & CTRL_TYPE_TOP) != 0) { - mRepositionTaskBounds.top += deltaY; + final int candidateTop = mRepositionTaskBounds.top + (int) deltaY; + mRepositionTaskBounds.top = (candidateTop > stableBounds.top) + ? candidateTop : oldTop; } if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { - mRepositionTaskBounds.bottom += deltaY; + final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY; + mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom) + ? candidateBottom : oldBottom; } if (mCtrlType == CTRL_TYPE_UNDEFINED) { mRepositionTaskBounds.offset((int) deltaX, (int) deltaY); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt index 8f66f4e7e47b..94c064bda763 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt @@ -5,13 +5,16 @@ import android.app.WindowConfiguration import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner +import android.view.Display import android.window.WindowContainerToken +import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING import androidx.test.filters.SmallTest import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED @@ -19,10 +22,11 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any import org.mockito.Mockito.argThat import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations /** @@ -51,6 +55,8 @@ class TaskPositionerTest : ShellTestCase() { private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock + private lateinit var mockDisplay: Display private lateinit var taskPositioner: TaskPositioner @@ -68,6 +74,9 @@ class TaskPositionerTest : ShellTestCase() { `when`(taskToken.asBinder()).thenReturn(taskBinder) `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) + `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(STABLE_BOUNDS) + } mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = TASK_ID @@ -78,6 +87,8 @@ class TaskPositionerTest : ShellTestCase() { displayId = DISPLAY_ID configuration.windowConfiguration.bounds = STARTING_BOUNDS } + mockWindowDecoration.mDisplay = mockDisplay + `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID } } @Test @@ -451,6 +462,72 @@ class TaskPositionerTest : ShellTestCase() { }) } + fun testDragResize_toDisallowedBounds_freezesAtLimit() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.bottom.toFloat() + ) + + // Resize the task by 10px to the right and bottom, a valid destination + val newBounds = Rect( + STARTING_BOUNDS.left, + STARTING_BOUNDS.top, + STARTING_BOUNDS.right + 10, + STARTING_BOUNDS.bottom + 10) + taskPositioner.onDragPositioningMove( + newBounds.right.toFloat(), + newBounds.bottom.toFloat() + ) + + // Resize the task by another 10px to the right (allowed) and to just in the disallowed + // area of the Y coordinate. + val newBounds2 = Rect( + newBounds.left, + newBounds.top, + newBounds.right + 10, + DISALLOWED_RESIZE_AREA.top + ) + taskPositioner.onDragPositioningMove( + newBounds2.right.toFloat(), + newBounds2.bottom.toFloat() + ) + + taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat()) + + // The first resize falls in the allowed area, verify there's a change for it. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds(newBounds) + } + }) + // The second resize falls in the disallowed area, verify there's no change for it. + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds(newBounds2) + } + }) + // Instead, there should be a change for its allowed portion (the X movement) with the Y + // staying frozen in the last valid resize position. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds( + Rect( + newBounds2.left, + newBounds2.top, + newBounds2.right, + newBounds.bottom // Stayed at the first resize destination. + ) + ) + } + }) + } + + private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean { + return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) && + bounds == configuration.windowConfiguration.bounds + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -458,6 +535,19 @@ class TaskPositionerTest : ShellTestCase() { private const val DENSITY_DPI = 20 private const val DEFAULT_MIN = 40 private const val DISPLAY_ID = 1 + private const val NAVBAR_HEIGHT = 50 + private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val DISALLOWED_RESIZE_AREA = Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom) + private val STABLE_BOUNDS = Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT + ) } } diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index 8266beb4ee8c..9a06be006dca 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -498,7 +498,7 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { return result; } -SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) { +SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) { ATRACE_CALL(); SkGainmapInfo gainmapInfo; std::unique_ptr<SkStream> gainmapStream; @@ -553,9 +553,12 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) { return SkCodec::kInternalError; } - // TODO: We don't currently parcel the gainmap, but if we should then also support - // the shared allocator - sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bm); + sk_sp<Bitmap> nativeBitmap; + if (isShared) { + nativeBitmap = Bitmap::allocateAshmemBitmap(&bm); + } else { + nativeBitmap = Bitmap::allocateHeapBitmap(&bm); + } if (!nativeBitmap) { ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(), bitmapInfo.height()); diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index 97573e1e8207..b3781b52a418 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -79,7 +79,7 @@ public: // Set whether the ImageDecoder should handle RestorePrevious frames. void setHandleRestorePrevious(bool handle); - SkCodec::Result extractGainmap(Bitmap* destination); + SkCodec::Result extractGainmap(Bitmap* destination, bool isShared); private: // State machine for keeping track of how to handle RestorePrevious (RP) diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index ad80460da5a9..db1c188e425e 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -354,7 +354,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // cost of RAM if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) { // The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead - result = decoder->extractGainmap(nativeBitmap.get()); + result = decoder->extractGainmap(nativeBitmap.get(), + allocator == kSharedMemory_Allocator ? true : false); jexception = get_and_clear_exception(env); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 113c85802fa2..3b00f20e724a 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -116,7 +116,7 @@ interface ITvInputManager { // For ad request void requestAd(in IBinder sessionToken, in AdRequest request, int userId); - void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId); + void notifyAdBufferReady(in IBinder sessionToken, in AdBuffer buffer, int userId); // For TV Message void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 165a9ddc26e9..6d65e1f95271 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -75,7 +75,7 @@ oneway interface ITvInputSession { // For ad request void requestAd(in AdRequest request); - void notifyAdBuffer(in AdBuffer buffer); + void notifyAdBufferReady(in AdBuffer buffer); // For TV messages void notifyTvMessage(in String type, in Bundle data); diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 8389706b501c..2810a7e65067 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -275,7 +275,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand break; } case DO_NOTIFY_AD_BUFFER: { - mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj); + mTvInputSessionImpl.notifyAdBufferReady((AdBuffer) msg.obj); break; } case DO_NOTIFY_TV_MESSAGE: { @@ -465,7 +465,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override - public void notifyAdBuffer(AdBuffer buffer) { + public void notifyAdBufferReady(AdBuffer buffer) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer)); } diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java index fb4e99cb1098..c4fc26ef1932 100644 --- a/media/java/android/media/tv/TableResponse.java +++ b/media/java/android/media/tv/TableResponse.java @@ -64,7 +64,10 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel * @param tableUri The URI of the table in the database. * @param version The version number of requested table. * @param size The Size number of table in bytes. + * + * @deprecated use {@link Builder} instead. */ + @Deprecated public TableResponse(int requestId, int sequence, @ResponseResult int responseResult, @Nullable Uri tableUri, int version, int size) { super(RESPONSE_TYPE, requestId, sequence, responseResult); @@ -76,30 +79,7 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel } /** - * Constructs a TableResponse with a table URI. - * - * @param requestId The ID is used to associate the response with the request. - * @param sequence The sequence number which indicates the order of related responses. - * @param responseResult The result for the response. It's one of {@link #RESPONSE_RESULT_OK}, - * {@link #RESPONSE_RESULT_CANCEL}, {@link #RESPONSE_RESULT_ERROR}. - * @param tableByteArray The byte array which stores the table in bytes. The structure and - * syntax of the table depends on the table name in - * {@link TableRequest#getTableName()} and the corresponding standard. - * @param version The version number of requested table. - * @param size The Size number of table in bytes. - */ - public TableResponse(int requestId, int sequence, @ResponseResult int responseResult, - @NonNull byte[] tableByteArray, int version, int size) { - super(RESPONSE_TYPE, requestId, sequence, responseResult); - mVersion = version; - mSize = size; - mTableUri = null; - mTableByteArray = tableByteArray; - mTableSharedMemory = null; - } - - /** - * Constructs a TableResponse with a table URI. + * Constructs a TableResponse. * * @param requestId The ID is used to associate the response with the request. * @param sequence The sequence number which indicates the order of related responses. @@ -112,17 +92,128 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel * {@link TableRequest#getTableName()} and the corresponding standard. * @param version The version number of requested table. * @param size The Size number of table in bytes. + * @param tableUri The URI of the table in the database. + * @param tableByteArray The byte array which stores the table in bytes. The structure and + * syntax of the table depends on the table name in + * @param tableSharedMemory The shared memory which stores the table. The table size can be + * large so using a shared memory optimizes the data + * communication between the table data source and the receiver. The + * structure syntax of the table depends on the table name in + * {@link TableRequest#getTableName()} and the corresponding standard. */ - public TableResponse(int requestId, int sequence, @ResponseResult int responseResult, - @NonNull SharedMemory tableSharedMemory, int version, int size) { + private TableResponse(int requestId, int sequence, @ResponseResult int responseResult, + int version, int size, Uri tableUri, byte[] tableByteArray, + SharedMemory tableSharedMemory) { super(RESPONSE_TYPE, requestId, sequence, responseResult); mVersion = version; mSize = size; - mTableUri = null; - mTableByteArray = null; + mTableUri = tableUri; + mTableByteArray = tableByteArray; mTableSharedMemory = tableSharedMemory; } + /** + * Builder for {@link TableResponse}. + */ + public static final class Builder { + private final int mRequestId; + private final int mSequence; + @ResponseResult + private final int mResponseResult; + private final int mVersion; + private final int mSize; + private Uri mTableUri; + private byte[] mTableByteArray; + private SharedMemory mTableSharedMemory; + + /** + * Constructs a Builder object of {@link TableResponse}. + * + * @param requestId The ID is used to associate the response with the request. + * @param sequence The sequence number which indicates the order of related responses. + * @param responseResult The result for the response. It's one of + * {@link #RESPONSE_RESULT_OK}, {@link #RESPONSE_RESULT_CANCEL}, + * {@link #RESPONSE_RESULT_ERROR}. + * @param version The version number of requested table. + * @param size The Size number of table in bytes. + */ + public Builder(int requestId, int sequence, @ResponseResult int responseResult, int version, + int size) { + mRequestId = requestId; + mSequence = sequence; + mResponseResult = responseResult; + mVersion = version; + mSize = size; + } + + /** + * Sets table URI. + * + * <p>For a single builder instance, at most one of table URI, table byte array, and table + * shared memory can be set. If more than one are set, only the last call takes precedence + * and others are reset to {@code null}. + * + * @param uri The URI of the table. + */ + @NonNull + public Builder setTableUri(@NonNull Uri uri) { + mTableUri = uri; + mTableByteArray = null; + mTableSharedMemory = null; + return this; + } + + /** + * Sets table byte array. + * + * <p>For a single builder instance, at most one of table URI, table byte array, and table + * shared memory can be set. If more than one are set, only the last call takes precedence + * and others are reset to {@code null}. + * + * @param bytes The byte array which stores the table in bytes. The structure and + * syntax of the table depends on the table name in + * {@link TableRequest#getTableName()} and the corresponding standard. + */ + @NonNull + public Builder setTableByteArray(@NonNull byte[] bytes) { + mTableByteArray = bytes; + mTableUri = null; + mTableSharedMemory = null; + return this; + } + + + /** + * Sets table shared memory. + * + * <p>For a single builder instance, at most one of table URI, table byte array, and table + * shared memory can be set. If more than one are set, only the last call takes precedence + * and others are reset to {@code null}. + * + * @param sharedMemory The shared memory which stores the table. The table size can be + * large so using a shared memory optimizes the data + * communication between the table data source and the receiver. The + * structure syntax of the table depends on the table name in + * {@link TableRequest#getTableName()} and the corresponding standard. + */ + @NonNull + public Builder setTableSharedMemory(@NonNull SharedMemory sharedMemory) { + mTableSharedMemory = sharedMemory; + mTableUri = null; + mTableByteArray = null; + return this; + } + + /** + * Builds a {@link TableResponse} object. + */ + @NonNull + public TableResponse build() { + return new TableResponse(mRequestId, mSequence, mResponseResult, mVersion, mSize, + mTableUri, mTableByteArray, mTableSharedMemory); + } + } + TableResponse(Parcel source) { super(RESPONSE_TYPE, source); String uriString = source.readString(); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 55a753f663c8..97403a44f75f 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -3642,13 +3642,13 @@ public final class TvInputManager { /** * Notifies when the advertisement buffer is filled and ready to be read. */ - public void notifyAdBuffer(AdBuffer buffer) { + public void notifyAdBufferReady(AdBuffer buffer) { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.notifyAdBuffer(mToken, buffer, mUserId); + mService.notifyAdBufferReady(mToken, buffer, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 9f40d704f7ac..afc124028ce2 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1351,7 +1351,7 @@ public abstract class TvInputService extends Service { * * @param buffer The {@link AdBuffer} that became ready for playback. */ - public void onAdBuffer(@NonNull AdBuffer buffer) { + public void onAdBufferReady(@NonNull AdBuffer buffer) { } /** @@ -2050,8 +2050,8 @@ public abstract class TvInputService extends Service { onRequestAd(request); } - void notifyAdBuffer(AdBuffer buffer) { - onAdBuffer(buffer); + void notifyAdBufferReady(AdBuffer buffer) { + onAdBufferReady(buffer); } void onTvMessageReceived(String type, Bundle data) { diff --git a/media/java/android/media/tv/TvRecordingInfo.java b/media/java/android/media/tv/TvRecordingInfo.java index 60ceb8394159..59915fc078c0 100644 --- a/media/java/android/media/tv/TvRecordingInfo.java +++ b/media/java/android/media/tv/TvRecordingInfo.java @@ -131,7 +131,6 @@ public final class TvRecordingInfo implements Parcelable { * cause the recording to start later than the specified time. This should cause the actual * duration of the recording to decrease. */ - @NonNull public long getStartPaddingMillis() { return mStartPaddingMillis; } @@ -144,7 +143,6 @@ public final class TvRecordingInfo implements Parcelable { * cause the recording to end earlier than the specified time. This should cause the actual * duration of the recording to decrease. */ - @NonNull public long getEndPaddingMillis() { return mEndPaddingMillis; } @@ -176,7 +174,6 @@ public final class TvRecordingInfo implements Parcelable { * https://www.oipf.tv/docs/OIPF-T1-R2_Specification-Volume-5-Declarative-Application-Environment-v2_3-2014-01-24.pdf * ">Open IPTV Forum Release 2 Specification</a>. It is described in Volume 5, section 7.10.1.1. */ - @NonNull @DaysOfWeek public int getRepeatDays() { return mRepeatDays; @@ -228,7 +225,6 @@ public final class TvRecordingInfo implements Parcelable { * Returns the scheduled start time of the recording in milliseconds since the epoch. */ @IntRange(from = 0) - @NonNull public long getScheduledStartTimeMillis() { return mScheduledStartTimeMillis; } @@ -237,7 +233,6 @@ public final class TvRecordingInfo implements Parcelable { * Returns the scheduled duration of the recording in milliseconds since the epoch. */ @IntRange(from = 0) - @NonNull public long getScheduledDurationMillis() { return mScheduledDurationMillis; } @@ -292,7 +287,6 @@ public final class TvRecordingInfo implements Parcelable { * <p> Returns -1 for recordings that have not yet started. */ @IntRange(from = -1) - @NonNull public long getRecordingStartTimeMillis() { return mRecordingStartTimeMillis; } @@ -306,7 +300,6 @@ public final class TvRecordingInfo implements Parcelable { * <p> Returns -1 for recordings that have not yet started. */ @IntRange(from = -1) - @NonNull public long getRecordingDurationMillis() { return mRecordingDurationMillis; } diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index 36954ad00f5f..77391841c6fe 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -39,7 +39,7 @@ oneway interface ITvInteractiveAppClient { void onSessionStateChanged(int state, int err, int seq); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq); void onTeletextAppStateChanged(int state, int seq); - void onAdBuffer(in AdBuffer buffer, int seq); + void onAdBufferReady(in AdBuffer buffer, int seq); void onCommandRequest(in String cmdType, in Bundle parameters, int seq); void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters, int seq); void onSetVideoBounds(in Rect rect, int seq); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl index 7db860489e8a..9e43e79144fd 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -38,7 +38,7 @@ oneway interface ITvInteractiveAppSessionCallback { void onSessionStateChanged(int state, int err); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId); void onTeletextAppStateChanged(int state); - void onAdBuffer(in AdBuffer buffer); + void onAdBufferReady(in AdBuffer buffer); void onCommandRequest(in String cmdType, in Bundle parameters); void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters); void onSetVideoBounds(in Rect rect); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java index 6eed4832847f..705e6c4e960c 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java @@ -558,7 +558,7 @@ public class ITvInteractiveAppSessionWrapper @Override public void notifyRecordingStarted(String recordingId, String requestId) { mCaller.executeOrSendMessage(mCaller.obtainMessageOO( - DO_NOTIFY_RECORDING_STARTED, recordingId, recordingId)); + DO_NOTIFY_RECORDING_STARTED, recordingId, requestId)); } @Override diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index 3e31bce30baa..1b26deff3822 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -681,14 +681,14 @@ public final class TvInteractiveAppManager { } @Override - public void onAdBuffer(AdBuffer buffer, int seq) { + public void onAdBufferReady(AdBuffer buffer, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Log.e(TAG, "Callback not found for seq " + seq); return; } - record.postAdBuffer(buffer); + record.postAdBufferReady(buffer); } } }; @@ -2245,12 +2245,12 @@ public final class TvInteractiveAppManager { }); } - void postAdBuffer(AdBuffer buffer) { + void postAdBufferReady(AdBuffer buffer) { mHandler.post(new Runnable() { @Override public void run() { if (mSession.getInputSession() != null) { - mSession.getInputSession().notifyAdBuffer(buffer); + mSession.getInputSession().notifyAdBufferReady(buffer); } } }); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 1ae82f43b9dd..d78d277edd8a 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -1507,8 +1507,7 @@ public abstract class TvInteractiveAppService extends Service { * @param type The type of recording to retrieve. */ @CallSuper - public void requestTvRecordingInfoList(@NonNull @TvRecordingInfo.TvRecordingListType - int type) { + public void requestTvRecordingInfoList(@TvRecordingInfo.TvRecordingListType int type) { executeOrPostRunnableOnMainThread(() -> { try { if (DEBUG) { @@ -1942,7 +1941,7 @@ public abstract class TvInteractiveAppService extends Service { * @param buffer The {@link AdBuffer} to be received */ @CallSuper - public void notifyAdBuffer(@NonNull AdBuffer buffer) { + public void notifyAdBufferReady(@NonNull AdBuffer buffer) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1950,10 +1949,10 @@ public abstract class TvInteractiveAppService extends Service { try { if (DEBUG) { Log.d(TAG, - "notifyAdBuffer(buffer=" + buffer + ")"); + "notifyAdBufferReady(buffer=" + buffer + ")"); } if (mSessionCallback != null) { - mSessionCallback.onAdBuffer(buffer); + mSessionCallback.onAdBufferReady(buffer); } } catch (RemoteException e) { Log.w(TAG, "error in notifyAdBuffer", e); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 0a8de127b6c9..a894cd800e81 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -682,7 +682,7 @@ public class TvInteractiveAppView extends ViewGroup { Log.d(TAG, "notifyRecordingStarted"); } if (mSession != null) { - mSession.notifyRecordingStarted(recordingId, recordingId); + mSession.notifyRecordingStarted(recordingId, requestId); } } @@ -1350,7 +1350,7 @@ public class TvInteractiveAppView extends ViewGroup { */ public void onRequestTvRecordingInfoList( @NonNull String iAppServiceId, - @NonNull @TvRecordingInfo.TvRecordingListType int type) { + @TvRecordingInfo.TvRecordingListType int type) { } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 16827da39b14..76f10a7c80f3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -446,6 +446,7 @@ fun MoreOptionsRowIntroCard( BodyMediumText(text = stringResource( R.string.use_provider_for_all_description, entryInfo.userProviderDisplayName)) } + item { Divider(thickness = 24.dp, color = Color.Transparent) } item { CtaButtonRow( leftButton = { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java index db6cc1a39ef1..7f3b0ff8c838 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java @@ -16,7 +16,6 @@ package com.android.providers.settings; -import android.annotation.NonNull; import android.os.Bundle; import android.provider.Settings; import android.util.ArrayMap; @@ -60,10 +59,6 @@ final class GenerationRegistry { // Maximum size of an individual backing store static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize(); - // Use an empty string to track the generation number of all non-predefined, unset settings - // The generation number is only increased when a new non-predefined setting is inserted - private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = ""; - public GenerationRegistry(Object lock) { mLock = lock; } @@ -77,10 +72,6 @@ final class GenerationRegistry { (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; - incrementGenerationInternal(key, indexMapKey); - } - - private void incrementGenerationInternal(int key, @NonNull String indexMapKey) { synchronized (mLock) { final MemoryIntArray backingStore = getBackingStoreLocked(key, /* createIfNotExist= */ false); @@ -96,8 +87,7 @@ final class GenerationRegistry { final int generation = backingStore.get(index) + 1; backingStore.set(index, generation); if (DEBUG) { - Slog.i(LOG_TAG, "Incremented generation for " - + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey) + Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey + " key:" + SettingsState.keyToString(key) + " at index:" + index); } @@ -108,18 +98,6 @@ final class GenerationRegistry { } } - // A new, non-predefined setting has been inserted, increment the tracking number for all unset - // settings - public void incrementGenerationForUnsetSettings(int key) { - final boolean isConfig = - (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG); - if (isConfig) { - // No need to track new settings for configs - return; - } - incrementGenerationInternal(key, DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS); - } - /** * 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 @@ -146,8 +124,8 @@ final class GenerationRegistry { bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, backingStore.get(index)); if (DEBUG) { - Slog.i(LOG_TAG, "Exported index:" + index + " for " - + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey) + Slog.i(LOG_TAG, "Exported index:" + index + + " for setting:" + indexMapKey + " key:" + SettingsState.keyToString(key)); } } catch (IOException e) { @@ -157,10 +135,6 @@ final class GenerationRegistry { } } - public void addGenerationDataForUnsetSettings(Bundle bundle, int key) { - addGenerationData(bundle, key, /* indexMapKey= */ DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS); - } - public void onUserRemoved(int userId) { final int secureKey = SettingsState.makeKey( SettingsState.SETTINGS_TYPE_SECURE, userId); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 27c8cdfe98f3..ba275ebca168 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2327,15 +2327,11 @@ public class SettingsProvider extends ContentProvider { result.putString(Settings.NameValueTable.VALUE, (setting != null && !setting.isNull()) ? setting.getValue() : null); - synchronized (mLock) { - if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) { - // Individual generation tracking for predefined settings even if they are unset + 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); - } else { - // All non-predefined, unset settings are tracked using the same generation number - mSettingsRegistry.mGenerationRegistry.addGenerationDataForUnsetSettings(result, - SettingsState.makeKey(type, userId)); } } return result; @@ -2349,8 +2345,7 @@ public class SettingsProvider extends ContentProvider { } else if (type == SETTINGS_TYPE_SYSTEM) { return sAllSystemSettings.contains(name); } else { - // Consider all config settings predefined because they are used by system apps only - return type == SETTINGS_TYPE_CONFIG; + return false; } } @@ -2359,13 +2354,14 @@ public class SettingsProvider extends ContentProvider { 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) { - // Track generation even if namespace is empty because this is for system apps only mSettingsRegistry.mGenerationRegistry.addGenerationData(result, - SettingsState.makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM), - prefix); + mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG, + UserHandle.USER_SYSTEM).mKey, prefix); } } + return result; } @@ -3056,15 +3052,10 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); boolean success = false; - boolean wasUnsetNonPredefinedSetting = false; SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState != null) { - if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) { - wasUnsetNonPredefinedSetting = true; - } success = settingsState.insertSettingLocked(name, value, - tag, makeDefault, forceNonSystemPackage, packageName, - overrideableByRestore); + tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore); } if (success && criticalSettings != null && criticalSettings.contains(name)) { @@ -3073,11 +3064,6 @@ public class SettingsProvider extends ContentProvider { if (forceNotify || success) { notifyForSettingsChange(key, name); - if (wasUnsetNonPredefinedSetting) { - // Increment the generation number for all non-predefined, unset settings, - // because a new non-predefined setting has been inserted - mGenerationRegistry.incrementGenerationForUnsetSettings(key); - } } if (success) { logSettingChanged(userId, name, type, CHANGE_TYPE_INSERT); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 4d8705f135af..c3888268a61d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -759,12 +759,6 @@ final class SettingsState { mPackageToMemoryUsage.put(packageName, newSize); } - public boolean hasSetting(String name) { - synchronized (mLock) { - return hasSettingLocked(name); - } - } - @GuardedBy("mLock") private boolean hasSettingLocked(String name) { return mSettings.indexOfKey(name) >= 0; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java index 6ec8146baee0..d34fe6943153 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java @@ -151,26 +151,6 @@ public class GenerationRegistryTest { checkBundle(b, 0, 1, false); } - @Test - public void testUnsetSettings() 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(); - generationRegistry.addGenerationData(b, secureKey, testSecureSetting); - checkBundle(b, 0, 1, false); - generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); - checkBundle(b, 1, 1, false); - generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); - // Test that unset settings always have the same index - checkBundle(b, 1, 1, false); - generationRegistry.incrementGenerationForUnsetSettings(secureKey); - // Test that the generation number of the unset settings have increased - generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); - checkBundle(b, 1, 2, false); - } - - private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull) throws IOException { final MemoryIntArray array = getArray(b); diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml index 92c952794f57..401ac73f3e0a 100644 --- a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml +++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml @@ -14,8 +14,8 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" + android:width="32dp" + android:height="32dp" android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/colorControlNormal"> diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml index 209681f29548..9a850e8fb811 100644 --- a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml +++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml @@ -14,8 +14,8 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" + android:width="32dp" + android:height="32dp" android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/colorControlNormal"> diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml index a3dc816f6755..54322c562c5d 100644 --- a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml +++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml @@ -14,8 +14,8 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" + android:width="32dp" + android:height="32dp" android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/colorControlNormal"> diff --git a/packages/SystemUI/res/drawable/ic_open_in_full.xml b/packages/SystemUI/res/drawable/ic_open_in_full.xml index c7f3236f94cf..7081c4b91bca 100644 --- a/packages/SystemUI/res/drawable/ic_open_in_full.xml +++ b/packages/SystemUI/res/drawable/ic_open_in_full.xml @@ -14,8 +14,8 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" + android:width="24dp" + android:height="24dp" android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/colorControlNormal"> diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml index ae0f8f46599d..3d0741c80450 100644 --- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml +++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml @@ -35,7 +35,7 @@ android:text="@string/accessibility_magnifier_size" android:textAppearance="@style/TextAppearance.MagnificationSetting.Title" android:focusable="true" - android:layout_gravity="center_vertical|left" /> + android:layout_gravity="center_vertical" /> <Button android:id="@+id/magnifier_edit_button" @@ -45,25 +45,22 @@ android:text="@string/accessibility_magnifier_edit" android:textAppearance="@style/TextAppearance.MagnificationSetting.EditButton" android:focusable="true" - android:layout_gravity="right" /> + android:layout_gravity="center_vertical" /> </LinearLayout> <LinearLayout android:background="@drawable/accessibility_magnification_setting_view_image_btn_layout_bg" - android:layout_width="@dimen/magnification_setting_image_button_background_width" + android:layout_width="match_parent" android:layout_height="@dimen/magnification_setting_image_button_height" + android:minWidth="@dimen/magnification_setting_image_button_background_width" android:orientation="horizontal"> <ImageButton android:id="@+id/magnifier_small_button" android:layout_width="0dp" android:layout_height="@dimen/magnification_setting_image_button_height" android:layout_weight="1" - android:scaleType="fitCenter" + android:scaleType="center" android:background="@drawable/accessibility_magnification_setting_view_image_btn_bg" - android:paddingLeft="@dimen/magnification_setting_image_button_padding_horizontal" - android:paddingRight="@dimen/magnification_setting_image_button_padding_horizontal" - android:paddingTop="@dimen/magnification_setting_image_button_padding_vertical" - android:paddingBottom="@dimen/magnification_setting_image_button_padding_vertical" android:src="@drawable/ic_magnification_menu_small" android:tint="@color/accessibility_magnification_image_button_tint" android:tintMode="src_atop" /> @@ -73,12 +70,8 @@ android:layout_width="0dp" android:layout_height="@dimen/magnification_setting_image_button_height" android:layout_weight="1" - android:scaleType="fitCenter" + android:scaleType="center" android:background="@drawable/accessibility_magnification_setting_view_image_btn_bg" - android:paddingLeft="@dimen/magnification_setting_image_button_padding_horizontal" - android:paddingRight="@dimen/magnification_setting_image_button_padding_horizontal" - android:paddingTop="@dimen/magnification_setting_image_button_padding_vertical" - android:paddingBottom="@dimen/magnification_setting_image_button_padding_vertical" android:src="@drawable/ic_magnification_menu_medium" android:tint="@color/accessibility_magnification_image_button_tint" android:tintMode="src_atop" /> @@ -88,12 +81,8 @@ android:layout_width="0dp" android:layout_height="@dimen/magnification_setting_image_button_height" android:layout_weight="1" - android:scaleType="fitCenter" + android:scaleType="center" android:background="@drawable/accessibility_magnification_setting_view_image_btn_bg" - android:paddingLeft="@dimen/magnification_setting_image_button_padding_horizontal" - android:paddingRight="@dimen/magnification_setting_image_button_padding_horizontal" - android:paddingTop="@dimen/magnification_setting_image_button_padding_vertical" - android:paddingBottom="@dimen/magnification_setting_image_button_padding_vertical" android:src="@drawable/ic_magnification_menu_large" android:tint="@color/accessibility_magnification_image_button_tint" android:tintMode="src_atop" /> @@ -103,16 +92,8 @@ android:layout_width="0dp" android:layout_height="@dimen/magnification_setting_image_button_height" android:layout_weight="1" - android:scaleType="fitCenter" + android:scaleType="center" android:background="@drawable/accessibility_magnification_setting_view_image_btn_bg" - android:paddingLeft - ="@dimen/magnification_setting_image_button_open_in_full_padding_horizontal" - android:paddingRight - ="@dimen/magnification_setting_image_button_open_in_full_padding_horizontal" - android:paddingTop - ="@dimen/magnification_setting_image_button_open_in_full_padding_vertical" - android:paddingBottom - ="@dimen/magnification_setting_image_button_open_in_full_padding_vertical" android:src="@drawable/ic_open_in_full" android:tint="@color/accessibility_magnification_image_button_tint" android:tintMode="src_atop" /> @@ -130,7 +111,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:singleLine="true" android:text="@string/accessibility_allow_diagonal_scrolling" android:textAppearance="@style/TextAppearance.MagnificationSetting.Title" android:layout_gravity="center_vertical" /> @@ -166,16 +146,11 @@ <Button android:id="@+id/magnifier_done_button" android:background="@drawable/accessibility_window_magnification_button_done_bg" - android:minHeight="@dimen/magnification_setting_button_done_height" - android:layout_width="@dimen/magnification_setting_button_done_width" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/accessibility_magnification_done" android:textAppearance="@style/TextAppearance.MagnificationSetting.DoneButton" android:focusable="true" android:layout_gravity="center_horizontal" - android:layout_marginTop="@dimen/magnification_setting_view_margin" - android:paddingLeft="@dimen/magnification_setting_button_done_padding_horizontal" - android:paddingRight="@dimen/magnification_setting_button_done_padding_horizontal" - android:paddingTop="@dimen/magnification_setting_button_done_padding_vertical" - android:paddingBottom="@dimen/magnification_setting_button_done_padding_vertical" /> + android:layout_marginTop="@dimen/magnification_setting_view_margin"/> </LinearLayout> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 2cd217395588..6596ed2cbb05 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1358,6 +1358,7 @@ <item name="android:fontFamily">google-sans</item> <item name="android:textColor">?androidprv:attr/textColorPrimary</item> <item name="android:textSize">@dimen/magnification_setting_text_size</item> + <item name="android:singleLine">true</item> </style> <style name="TextAppearance.MagnificationSetting.EditButton"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 2f937a949e78..ba5a8c94dc23 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -39,6 +39,7 @@ import static java.lang.Integer.max; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; @@ -1067,10 +1068,14 @@ public class KeyguardSecurityContainer extends ConstraintLayout { int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); + AnimatorSet anims = new AnimatorSet(); ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); - yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE); - yAnim.setDuration(500); - yAnim.start(); + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, + 0f); + + anims.setInterpolator(Interpolators.STANDARD_ACCELERATE); + anims.playTogether(alphaAnim, yAnim); + anims.start(); } private void setupUserSwitcher() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 06258b20e06f..98866c694526 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -127,6 +127,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private View.OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event); private ActivityStarter.OnDismissAction mDismissAction; private Runnable mCancelAction; + private boolean mWillRunDismissFromKeyguard; private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -262,8 +263,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard // If there's a pending runnable because the user interacted with a widget // and we're leaving keyguard, then run it. boolean deferKeyguardDone = false; + mWillRunDismissFromKeyguard = false; if (mDismissAction != null) { deferKeyguardDone = mDismissAction.onDismiss(); + mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard(); mDismissAction = null; mCancelAction = null; } @@ -527,6 +530,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } /** + * @return will the dismissal run from the keyguard layout (instead of from bouncer) + */ + public boolean willRunDismissFromKeyguard() { + return mWillRunDismissFromKeyguard; + } + + /** * Remove any dismiss action or cancel action that was set. */ public void cancelDismissAction() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 64fe645b9c28..aaf6307ebc96 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -2459,8 +2459,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty() && mBiometricEnabledForUser.get(userId) && mAuthController.isFaceAuthEnrolled(userId); + if (mIsFaceEnrolled != isFaceEnrolled) { + mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled); + } mIsFaceEnrolled = isFaceEnrolled; - mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled); } public boolean isFaceSupported() { @@ -3132,13 +3134,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting boolean isUnlockWithFingerprintPossible(int userId) { // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once. - boolean fpEnrolled = mFpm != null + boolean newFpEnrolled = mFpm != null && !mFingerprintSensorProperties.isEmpty() && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId); - mLogger.logFpEnrolledUpdated(userId, - mIsUnlockWithFingerprintPossible.getOrDefault(userId, false), - fpEnrolled); - mIsUnlockWithFingerprintPossible.put(userId, fpEnrolled); + Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); + if (oldFpEnrolled != newFpEnrolled) { + mLogger.logFpEnrolledUpdated(userId, oldFpEnrolled, newFpEnrolled); + } + mIsUnlockWithFingerprintPossible.put(userId, newFpEnrolled); return mIsUnlockWithFingerprintPossible.get(userId); } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index bfd99d355a2a..1ae380e53c52 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -51,6 +51,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import com.android.settingslib.udfps.UdfpsOverlayParams; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; @@ -778,7 +779,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } @Override - public void onUdfpsLocationChanged() { + public void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) { updateUdfpsConfig(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index d811d30b5693..57c99187c302 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -403,15 +403,21 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mAnimationController.deleteWindowMagnification(animationCallback); } + void deleteWindowMagnification() { + deleteWindowMagnification(/* closeSettingPanel= */ true); + } + /** * Deletes the magnification window. */ - void deleteWindowMagnification() { + void deleteWindowMagnification(boolean closeSettingPanel) { if (!isWindowVisible()) { return; } - closeMagnificationSettings(); + if (closeSettingPanel) { + closeMagnificationSettings(); + } if (mMirrorSurface != null) { mTransaction.remove(mMirrorSurface).apply(); @@ -489,7 +495,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold // Recreate the window again to correct the window appearance due to density or // window size changed not caused by rotation. if (isWindowVisible() && reCreateWindow) { - deleteWindowMagnification(); + deleteWindowMagnification(/* closeSettingPanel= */ false); enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java index 9ad64e293fe5..d9f5544c39b9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java @@ -230,14 +230,6 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest } }; - private void applyResourcesValuesWithDensityChanged() { - if (mIsVisible) { - // Reset button to make its window layer always above the mirror window. - hideSettingPanel(); - showSettingPanel(false); - } - } - private boolean onTouch(View v, MotionEvent event) { if (!mIsVisible) { return false; @@ -354,10 +346,6 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest } mWindowManager.addView(mSettingView, mParams); - if (resetPosition) { - // Request focus on the settings panel when position of the panel is reset. - mSettingView.requestFocus(); - } // Exclude magnification switch button from system gesture area. setSystemGestureExclusion(); @@ -445,7 +433,15 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest void onConfigurationChanged(int configDiff) { if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0 - || (configDiff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) { + || (configDiff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0 + || (configDiff & ActivityInfo.CONFIG_FONT_SCALE) != 0 + || (configDiff & ActivityInfo.CONFIG_DENSITY) != 0) { + // We listen to following config changes to trigger layout inflation: + // CONFIG_UI_MODE: theme change + // CONFIG_ASSETS_PATHS: wallpaper change + // CONFIG_FONT_SCALE: font size change + // CONFIG_DENSITY: display size change + boolean showSettingPanelAfterThemeChange = mIsVisible; hideSettingPanel(/* resetPosition= */ false); inflateView(); @@ -454,6 +450,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest } return; } + if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds); mDraggableWindowBounds.set(getDraggableWindowBounds()); @@ -465,10 +462,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest + mDraggableWindowBounds.top; return; } - if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) { - applyResourcesValuesWithDensityChanged(); - return; - } + if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) { updateAccessibilityWindowTitle(); return; @@ -536,7 +530,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, - /* _flags= */ 0, + LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); params.gravity = Gravity.TOP | Gravity.START; params.accessibilityTitle = getAccessibilityWindowTitle(context); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 08efd89029ed..c31d45ff0b83 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -148,6 +148,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull private final WindowManager mWindowManager; @NonNull private final DisplayManager mDisplayManager; @Nullable private UdfpsController mUdfpsController; + @Nullable private UdfpsOverlayParams mUdfpsOverlayParams; @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback; @Nullable private SideFpsController mSideFpsController; @Nullable private UdfpsLogger mUdfpsLogger; @@ -806,6 +807,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0); final Rect previousUdfpsBounds = mUdfpsBounds; + final UdfpsOverlayParams previousUdfpsOverlayParams = mUdfpsOverlayParams; + mUdfpsBounds = udfpsProp.getLocation().getRect(); mUdfpsBounds.scale(mScaleFactor); @@ -815,7 +818,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mCachedDisplayInfo.getNaturalWidth(), /* right */ mCachedDisplayInfo.getNaturalHeight() /* botom */); - final UdfpsOverlayParams overlayParams = new UdfpsOverlayParams( + mUdfpsOverlayParams = new UdfpsOverlayParams( mUdfpsBounds, overlayBounds, mCachedDisplayInfo.getNaturalWidth(), @@ -823,10 +826,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mScaleFactor, mCachedDisplayInfo.rotation); - mUdfpsController.updateOverlayParams(udfpsProp, overlayParams); - if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) { + mUdfpsController.updateOverlayParams(udfpsProp, mUdfpsOverlayParams); + if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds) || !Objects.equals( + previousUdfpsOverlayParams, mUdfpsOverlayParams)) { for (Callback cb : mCallbacks) { - cb.onUdfpsLocationChanged(); + cb.onUdfpsLocationChanged(mUdfpsOverlayParams); } } } @@ -1336,7 +1340,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, * On devices with UDFPS, this is always called alongside * {@link #onFingerprintLocationChanged}. */ - default void onUdfpsLocationChanged() {} + default void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) {} /** * Called when the location of the face unlock sensor (typically the front facing camera) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 93b57dc127dd..fbb6451973c7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.logging.KeyguardLogger import com.android.settingslib.Utils +import com.android.settingslib.udfps.UdfpsOverlayParams import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.flags.FeatureFlags @@ -326,7 +327,7 @@ class AuthRippleController @Inject constructor( updateUdfpsDependentParams() } - override fun onUdfpsLocationChanged() { + override fun onUdfpsLocationChanged(udfpsOverlayParams: UdfpsOverlayParams) { updateUdfpsDependentParams() } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 9e8326400e13..bb35355ba03a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -100,6 +100,8 @@ import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.SystemClock; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -110,8 +112,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; - /** * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events, * and toggles the UDFPS display mode. @@ -598,14 +598,20 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; + case UNCHANGED: + if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getX(), event.getY(), + true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID + && event.getActionMasked() == MotionEvent.ACTION_DOWN + && mAlternateBouncerInteractor.isVisibleState()) { + // No pointer on sensor, forward to keyguard if alternateBouncer is visible + mKeyguardViewManager.onTouch(event); + } + default: break; } logBiometricTouch(processedTouch.getEvent(), data); - // We should only consume touches that are within the sensor. By returning "false" for - // touches outside of the sensor, we let other UI components consume these events and act on - // them appropriately. return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds()); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt new file mode 100644 index 000000000000..92a7094c22bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.view.MotionEvent +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.settingslib.udfps.UdfpsOverlayParams +import com.android.systemui.biometrics.AuthController +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** Encapsulates business logic for interacting with the UDFPS overlay. */ +@SysUISingleton +class UdfpsOverlayInteractor +@Inject +constructor(private val authController: AuthController, @Application scope: CoroutineScope) { + + /** Whether a touch should be intercepted or allowed to pass to the UdfpsOverlay */ + fun canInterceptTouchInUdfpsBounds(ev: MotionEvent): Boolean { + val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) + val isWithinUdfpsOverlayBounds = + udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt()) + return !isUdfpsEnrolled || !isWithinUdfpsOverlayBounds + } + + /** Returns the current udfpsOverlayParams */ + val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val callback = + object : AuthController.Callback { + override fun onUdfpsLocationChanged( + udfpsOverlayParams: UdfpsOverlayParams + ) { + trySendWithFailureLogging( + udfpsOverlayParams, + TAG, + "update udfpsOverlayParams" + ) + } + } + authController.addCallback(callback) + awaitClose { authController.removeCallback(callback) } + } + .stateIn(scope, started = SharingStarted.Eagerly, initialValue = UdfpsOverlayParams()) + + companion object { + private const val TAG = "UdfpsOverlayInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b6edcf52b302..6209c0ba47de 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -485,6 +485,13 @@ object Flags { val ENABLE_PIP_APP_ICON_OVERLAY = sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true) + // TODO(b/272110828): Tracking bug + @Keep + @JvmField + val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = + sysPropBooleanFlag( + 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false) + // 1200 - predictive back @Keep @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index e43f83b876ba..07753ca1e6dc 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -788,6 +788,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public boolean onLongPress() { + // don't actually trigger the reboot if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS); if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.reboot(true); @@ -808,6 +813,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void onPress() { + // don't actually trigger the shutdown if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS); // shutdown by making sure radio and power are handled accordingly. mWindowManagerFuncs.shutdown(); @@ -919,6 +929,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public boolean onLongPress() { + // don't actually trigger the reboot if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_LONG_PRESS); if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.reboot(true); @@ -939,6 +954,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void onPress() { + // don't actually trigger the reboot if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_PRESS); mWindowManagerFuncs.reboot(false); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 57c4b36b8b7a..3e52ff2c2da0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -380,10 +380,7 @@ class KeyguardUnlockAnimationController @Inject constructor( // If the launcher is underneath, but we're about to launch an activity, don't do // the animations since they won't be visible. !notificationShadeWindowController.isLaunchingActivity && - launcherUnlockController != null && - // Temporarily disable for foldables since foldable launcher has two first pages, - // which breaks the in-window animation. - !isFoldable(context) + launcherUnlockController != null } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt index faeb48526ae4..a2589d3d4116 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt @@ -52,6 +52,7 @@ interface BouncerViewDelegate { cancelAction: Runnable?, ) fun willDismissWithActions(): Boolean + fun willRunDismissFromKeyguard(): Boolean /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */ fun getBackCallback(): OnBackAnimationCallback } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 2dc8fee25379..1fbfff95ab7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -47,6 +47,7 @@ constructor( listenForOccludedToLockscreen() listenForOccludedToDreaming() listenForOccludedToAodOrDozing() + listenForOccludedToGone() } private fun listenForOccludedToDreaming() { @@ -72,11 +73,22 @@ constructor( private fun listenForOccludedToLockscreen() { scope.launch { keyguardInteractor.isKeyguardOccluded - .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { (isOccluded, lastStartedKeyguardState) -> + .sample( + combine( + keyguardInteractor.isKeyguardShowing, + keyguardTransitionInteractor.startedKeyguardTransitionStep, + ::Pair + ), + ::toTriple + ) + .collect { (isOccluded, isShowing, lastStartedKeyguardState) -> // Occlusion signals come from the framework, and should interrupt any // existing transition - if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) { + if ( + !isOccluded && + isShowing && + lastStartedKeyguardState.to == KeyguardState.OCCLUDED + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, @@ -90,6 +102,38 @@ constructor( } } + private fun listenForOccludedToGone() { + scope.launch { + keyguardInteractor.isKeyguardOccluded + .sample( + combine( + keyguardInteractor.isKeyguardShowing, + keyguardTransitionInteractor.startedKeyguardTransitionStep, + ::Pair + ), + ::toTriple + ) + .collect { (isOccluded, isShowing, lastStartedKeyguardState) -> + // Occlusion signals come from the framework, and should interrupt any + // existing transition + if ( + !isOccluded && + !isShowing && + lastStartedKeyguardState.to == KeyguardState.OCCLUDED + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.OCCLUDED, + KeyguardState.GONE, + getAnimator(), + ) + ) + } + } + } + } + private fun listenForOccludedToAodOrDozing() { scope.launch { keyguardInteractor.wakefulnessModel diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index d092337adfcb..3d2c472b8648 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -372,6 +372,11 @@ constructor( return primaryBouncerView.delegate?.willDismissWithActions() == true } + /** Will the dismissal run from the keyguard layout (instead of from bouncer) */ + fun willRunDismissFromKeyguard(): Boolean { + return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true + } + /** Returns whether the bouncer should be full screen. */ private fun needsFullscreenBouncer(): Boolean { val mode: KeyguardSecurityModel.SecurityMode = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt new file mode 100644 index 000000000000..1db77336109e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt @@ -0,0 +1,23 @@ +/* + * 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 + +/** Alpha values for scrim updates */ +data class ScrimAlpha( + val frontAlpha: Float = 0f, + val behindAlpha: Float = 0f, + val notificationsAlpha: Float = 0f, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 2337ffc35fa6..bb617bd50c69 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -97,6 +97,10 @@ object KeyguardBouncerViewBinder { override fun willDismissWithActions(): Boolean { return securityContainerController.hasDismissActions() } + + override fun willRunDismissFromKeyguard(): Boolean { + return securityContainerController.willRunDismissFromKeyguard() + } } view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index 92038e24edf3..b23247c30256 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -20,11 +20,14 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.statusbar.SysuiStatusBarStateController import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map /** * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to @@ -36,6 +39,7 @@ class PrimaryBouncerToGoneTransitionViewModel constructor( private val interactor: KeyguardTransitionInteractor, private val statusBarStateController: SysuiStatusBarStateController, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, ) { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -44,26 +48,49 @@ constructor( ) private var leaveShadeOpen: Boolean = false + private var willRunDismissFromKeyguard: Boolean = false /** Bouncer container alpha */ val bouncerAlpha: Flow<Float> = transitionAnimation.createFlow( duration = 200.milliseconds, - onStep = { 1f - it }, - ) - - /** Scrim behind alpha */ - val scrimBehindAlpha: Flow<Float> = - transitionAnimation.createFlow( - duration = TO_GONE_DURATION, - interpolator = EMPHASIZED_ACCELERATE, - onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() }, + onStart = { + willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard() + }, onStep = { - if (leaveShadeOpen) { - 1f + if (willRunDismissFromKeyguard) { + 0f } else { 1f - it } }, ) + + /** Scrim alpha values */ + val scrimAlpha: Flow<ScrimAlpha> = + transitionAnimation + .createFlow( + duration = TO_GONE_DURATION, + interpolator = EMPHASIZED_ACCELERATE, + onStart = { + leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() + willRunDismissFromKeyguard = + primaryBouncerInteractor.willRunDismissFromKeyguard() + }, + onStep = { 1f - it }, + ) + .map { + if (willRunDismissFromKeyguard) { + ScrimAlpha( + notificationsAlpha = 1f, + ) + } else if (leaveShadeOpen) { + ScrimAlpha( + behindAlpha = 1f, + notificationsAlpha = 1f, + ) + } else { + ScrimAlpha(behindAlpha = it) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index c130b3913b64..c09524bac613 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -36,6 +36,7 @@ import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; @@ -86,7 +87,7 @@ public class NotificationShadeWindowViewController { private final PulsingGestureListener mPulsingGestureListener; private final NotificationInsetsController mNotificationInsetsController; private final AlternateBouncerInteractor mAlternateBouncerInteractor; - + private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; @@ -134,6 +135,7 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, AlternateBouncerInteractor alternateBouncerInteractor, + UdfpsOverlayInteractor udfpsOverlayInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel ) { @@ -156,6 +158,7 @@ public class NotificationShadeWindowViewController { mPulsingGestureListener = pulsingGestureListener; mNotificationInsetsController = notificationInsetsController; mAlternateBouncerInteractor = alternateBouncerInteractor; + mUdfpsOverlayInteractor = udfpsOverlayInteractor; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -240,7 +243,6 @@ public class NotificationShadeWindowViewController { mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); - mStatusBarKeyguardViewManager.onTouch(ev); if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { // Disallow new pointers while the brightness mirror is visible. This is so that @@ -316,8 +318,8 @@ public class NotificationShadeWindowViewController { } if (mAlternateBouncerInteractor.isVisibleState()) { - // capture all touches if the alt auth bouncer is showing - return true; + // If using UDFPS, don't intercept touches that are within its overlay bounds + return mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(ev); } if (mLockIconViewController.onInterceptTouchEvent(ev)) { @@ -355,6 +357,7 @@ public class NotificationShadeWindowViewController { if (mAlternateBouncerInteractor.isVisibleState()) { // eat the touch + mStatusBarKeyguardViewManager.onTouch(ev); handled = 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 9f8bf358bfb6..0bded7327ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -59,13 +59,13 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; +import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; -import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -209,7 +209,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private final ScreenOffAnimationController mScreenOffAnimationController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final SysuiStatusBarStateController mStatusBarStateController; private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; @@ -270,12 +269,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private CoroutineDispatcher mMainDispatcher; private boolean mIsBouncerToGoneTransitionRunning = false; private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - private final Consumer<Float> mScrimAlphaConsumer = - (Float alpha) -> { + private final Consumer<ScrimAlpha> mScrimAlphaConsumer = + (ScrimAlpha alphas) -> { + mInFrontAlpha = alphas.getFrontAlpha(); mScrimInFront.setViewAlpha(mInFrontAlpha); + + mNotificationsAlpha = alphas.getNotificationsAlpha(); mNotificationsScrim.setViewAlpha(mNotificationsAlpha); - mBehindAlpha = alpha; - mScrimBehind.setViewAlpha(alpha); + + mBehindAlpha = alphas.getBehindAlpha(); + mScrimBehind.setViewAlpha(mBehindAlpha); }; Consumer<TransitionStep> mPrimaryBouncerToGoneTransition; @@ -297,7 +300,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump StatusBarKeyguardViewManager statusBarKeyguardViewManager, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, KeyguardTransitionInteractor keyguardTransitionInteractor, - SysuiStatusBarStateController sysuiStatusBarStateController, @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator, FeatureFlags featureFlags) { @@ -309,7 +311,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mKeyguardStateController = keyguardStateController; mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mStatusBarStateController = sysuiStatusBarStateController; mKeyguardVisibilityCallback = new KeyguardVisibilityCallback(); mHandler = handler; mMainExecutor = mainExecutor; @@ -409,7 +410,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(), mPrimaryBouncerToGoneTransition, mMainDispatcher); - collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(), + collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), mScrimAlphaConsumer, mMainDispatcher); } @@ -1127,8 +1128,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mBehindAlpha = 1; } // Prevent notification scrim flicker when transitioning away from keyguard. - if (mKeyguardStateController.isKeyguardGoingAway() - && !mStatusBarStateController.leaveOpenOnKeyguardHide()) { + if (mKeyguardStateController.isKeyguardGoingAway()) { mNotificationsAlpha = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 66f5b6508494..aa71b51b5459 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -733,7 +733,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else { showBouncerOrKeyguard(hideBouncerWhenShowing); } - hideAlternateBouncer(false); + if (hideBouncerWhenShowing) { + hideAlternateBouncer(false); + } mKeyguardUpdateManager.sendKeyguardReset(); updateStates(); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 064bc9c0036d..38d3a3eec606 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -521,6 +521,38 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void testWillRunDismissFromKeyguardIsTrue() { + ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class); + when(action.willRunAnimationOnKeyguard()).thenReturn(true); + mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */); + + mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */); + + assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue(); + } + + @Test + public void testWillRunDismissFromKeyguardIsFalse() { + ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class); + when(action.willRunAnimationOnKeyguard()).thenReturn(false); + mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */); + + mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */); + + assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse(); + } + + @Test + public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() { + mKeyguardSecurityContainerController.setOnDismissAction(null /* action */, + null /* cancelAction */); + + mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */); + + assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse(); + } + + @Test public void testOnStartingToHide() { mKeyguardSecurityContainerController.onStartingToHide(); verify(mInputViewController).onStartingToHide(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index f370ad6dc298..33f0ae5563f7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -36,6 +36,7 @@ import android.util.Pair; import androidx.test.filters.SmallTest; +import com.android.settingslib.udfps.UdfpsOverlayParams; import com.android.systemui.doze.util.BurnInHelperKt; import org.junit.Test; @@ -107,7 +108,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { Pair<Float, Point> udfps = setupUdfps(); // WHEN udfps location changes - mAuthControllerCallback.onUdfpsLocationChanged(); + mAuthControllerCallback.onUdfpsLocationChanged(new UdfpsOverlayParams()); mDelayableExecutor.runAllReady(); // THEN lock icon view location is updated with the same coordinates as auth controller vals diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java index 52a70ee9cce2..47c91911e52a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java @@ -201,13 +201,6 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { assertThat(magnifierMediumButton.isSelected()).isTrue(); } - @Test - public void showSettingPanel_focusOnThePanel() { - mWindowMagnificationSettings.showSettingPanel(); - - assertThat(mSettingView.isFocused()).isTrue(); - } - private <T extends View> T getInternalView(@IdRes int idRes) { T view = mSettingView.findViewById(idRes); assertNotNull(view); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 05266f1894ac..9a73898ca7c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1224,6 +1224,44 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void onTouch_WithNewTouchDetection_forwardToKeyguard() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + + // THEN the touch is forwarded to Keyguard + verify(mStatusBarKeyguardViewManager).onTouch(downEvent); + downEvent.recycle(); + } + + @Test public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { // GIVEN UDFPS overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt new file mode 100644 index 000000000000..87d5ae64dee8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt @@ -0,0 +1,116 @@ +/* + * 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.biometrics.domain.interactor + +import android.graphics.Rect +import android.test.suitebuilder.annotation.SmallTest +import android.view.MotionEvent +import android.view.Surface +import com.android.settingslib.udfps.UdfpsOverlayParams +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.coroutines.collectLastValue +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(JUnit4::class) +class UdfpsOverlayInteractorTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + private lateinit var testScope: TestScope + + @Mock private lateinit var authController: AuthController + @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback> + + @Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams + @Mock private lateinit var overlayBounds: Rect + + private lateinit var underTest: UdfpsOverlayInteractor + + @Before + fun setUp() { + testScope = TestScope(StandardTestDispatcher()) + } + + @Test + fun testShouldInterceptTouch() = + testScope.runTest { + createUdpfsOverlayInteractor() + + // When fingerprint enrolled and touch is within bounds + verify(authController).addCallback(authControllerCallback.capture()) + authControllerCallback.value.onUdfpsLocationChanged(udfpsOverlayParams) + whenever(authController.isUdfpsEnrolled(anyInt())).thenReturn(true) + whenever(udfpsOverlayParams.overlayBounds).thenReturn(overlayBounds) + whenever(overlayBounds.contains(downEv.x.toInt(), downEv.y.toInt())).thenReturn(true) + + runCurrent() + + // Then touch should not be intercepted + val canInterceptTrue = underTest.canInterceptTouchInUdfpsBounds(downEv) + assertThat(canInterceptTrue).isFalse() + + // When touch is outside of bounds + whenever(overlayBounds.contains(downEv.x.toInt(), downEv.y.toInt())).thenReturn(false) + + // Then touch should be intercepted + val canInterceptFalse = underTest.canInterceptTouchInUdfpsBounds(downEv) + assertThat(canInterceptFalse).isTrue() + } + + @Test + fun testUdfpsOverlayParamsChange() = + testScope.runTest { + createUdpfsOverlayInteractor() + val udfpsOverlayParams = collectLastValue(underTest.udfpsOverlayParams) + runCurrent() + + verify(authController).addCallback(authControllerCallback.capture()) + + // When udfpsLocationChanges in authcontroller + authControllerCallback.value.onUdfpsLocationChanged(firstParams) + + // Then the value in the interactor should be updated + assertThat(udfpsOverlayParams()).isEqualTo(firstParams) + } + + private fun createUdpfsOverlayInteractor() { + underTest = UdfpsOverlayInteractor(authController, testScope.backgroundScope) + testScope.runCurrent() + } +} + +private val firstParams = + UdfpsOverlayParams(Rect(0, 0, 10, 10), Rect(0, 0, 10, 10), 1, 1, 1f, Surface.ROTATION_0) +private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index fe9098fa5c25..fc3a6383cd88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -967,6 +967,92 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + @Test + fun `OCCLUDED to GONE`() = + testScope.runTest { + // GIVEN a device on lockscreen + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + reset(mockTransitionRepository) + + // WHEN keyguard goes away + keyguardRepository.setKeyguardShowing(false) + // AND occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to GONE should occur + assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.to).isEqualTo(KeyguardState.GONE) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `OCCLUDED to LOCKSCREEN`() = + testScope.runTest { + // GIVEN a device on lockscreen + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + reset(mockTransitionRepository) + + // WHEN occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to LOCKSCREEN should occur + assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + private fun startingToWake() = WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 2a91799741b7..746f66881a88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -21,7 +21,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -44,21 +46,86 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Before fun setUp() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() val interactor = KeyguardTransitionInteractor(repository) - underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController) + underTest = + PrimaryBouncerToGoneTransitionViewModel( + interactor, + statusBarStateController, + primaryBouncerInteractor + ) + + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false) + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) } @Test - fun scrimBehindAlpha_leaveShadeOpen() = + fun bouncerAlpha() = runTest(UnconfinedTestDispatcher()) { val values = mutableListOf<Float>() - val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this) + val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + + assertThat(values.size).isEqualTo(3) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun bouncerAlpha_runDimissFromKeyguard() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this) + + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + + assertThat(values.size).isEqualTo(3) + values.forEach { assertThat(it).isEqualTo(0f) } + + job.cancel() + } + + @Test + fun scrimAlpha_runDimissFromKeyguard() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<ScrimAlpha>() + + val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this) + + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) } + + job.cancel() + } + + @Test + fun scrimBehindAlpha_leaveShadeOpen() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<ScrimAlpha>() + + val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this) whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true) @@ -68,7 +135,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { repository.sendTransitionStep(step(1f)) assertThat(values.size).isEqualTo(4) - values.forEach { assertThat(it).isEqualTo(1f) } + values.forEach { + assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f)) + } job.cancel() } @@ -76,9 +145,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { @Test fun scrimBehindAlpha_doNotLeaveShadeOpen() = runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() + val values = mutableListOf<ScrimAlpha>() - val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this) + val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this) whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) @@ -88,8 +157,10 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { repository.sendTransitionStep(step(1f)) assertThat(values.size).isEqualTo(4) - values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - assertThat(values[3]).isEqualTo(0f) + values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) } + values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) } + values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) } + assertThat(values[3].behindAlpha).isEqualTo(0f) job.cancel() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 82a57438052f..51492eb8e532 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -26,6 +26,7 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.keyguard.KeyguardUnlockAnimationController @@ -86,6 +87,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var pulsingGestureListener: PulsingGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @@ -133,6 +135,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { keyguardBouncerViewModel, keyguardBouncerComponentFactory, alternateBouncerInteractor, + udfpsOverlayInteractor, keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, ) @@ -265,6 +268,17 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test + fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() { + // Down event within udfpsOverlay bounds while alternateBouncer is showing + whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + + // Then touch should not be intercepted + val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) + assertThat(shouldIntercept).isFalse() + } + + @Test fun testGetBouncerContainer() { Mockito.clearInvocations(view) underTest.bouncerContainer diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index faa6221b675c..2f528a86cc2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -40,6 +40,7 @@ import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; @@ -101,6 +102,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @Mock private NotificationInsetsController mNotificationInsetsController; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @@ -152,6 +154,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mKeyguardBouncerViewModel, mKeyguardBouncerComponentFactory, mAlternateBouncerInteractor, + mUdfpsOverlayInteractor, mKeyguardTransitionInteractor, mPrimaryBouncerToGoneTransitionViewModel ); @@ -177,6 +180,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we should intercept touch diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index d487ba93859a..7a1270f3521d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -71,7 +71,6 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator; -import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -134,7 +133,6 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private CoroutineDispatcher mMainDispatcher; - @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController; // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The // event-dispatch-on-registration pattern caused some of these unit tests to fail.) @@ -249,7 +247,7 @@ public class ScrimControllerTest extends SysuiTestCase { when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition()) .thenReturn(emptyFlow()); - when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha()) + when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha()) .thenReturn(emptyFlow()); mScrimController = new ScrimController( @@ -268,7 +266,6 @@ public class ScrimControllerTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mPrimaryBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, - mSysuiStatusBarStateController, mMainDispatcher, mLinearLargeScreenShadeInterpolator, mFeatureFlags); @@ -984,7 +981,6 @@ public class ScrimControllerTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mPrimaryBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, - mSysuiStatusBarStateController, mMainDispatcher, mLinearLargeScreenShadeInterpolator, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 158e9adcff43..e2019b2814d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -683,4 +683,30 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { // the following call before registering centralSurfaces should NOT throw a NPE: mStatusBarKeyguardViewManager.hideAlternateBouncer(true); } + + @Test + public void testResetHideBouncerWhenShowing_alternateBouncerHides() { + // GIVEN the keyguard is showing + reset(mAlternateBouncerInteractor); + when(mKeyguardStateController.isShowing()).thenReturn(true); + + // WHEN SBKV is reset with hideBouncerWhenShowing=true + mStatusBarKeyguardViewManager.reset(true); + + // THEN alternate bouncer is hidden + verify(mAlternateBouncerInteractor).hide(); + } + + @Test + public void testResetHideBouncerWhenShowingIsFalse_alternateBouncerHides() { + // GIVEN the keyguard is showing + reset(mAlternateBouncerInteractor); + when(mKeyguardStateController.isShowing()).thenReturn(true); + + // WHEN SBKV is reset with hideBouncerWhenShowing=false + mStatusBarKeyguardViewManager.reset(false); + + // THEN alternate bouncer is NOT hidden + verify(mAlternateBouncerInteractor, never()).hide(); + } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b54dbbf14ce2..7f6ad431c601 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -742,6 +742,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return Collections.EMPTY_LIST; } final String typeHints = mService.getMaster().getPccProviderHints(); + if (sVerbose) { + Slog.v(TAG, "TypeHints flag:" + typeHints); + } if (TextUtils.isEmpty(typeHints)) { return new ArrayList<>(); } @@ -757,7 +760,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void maybeRequestFieldClassificationFromServiceLocked() { if (mClassificationState.mPendingFieldClassificationRequest == null) { - Log.w(TAG, "Received AssistData without pending classification request"); + Slog.w(TAG, "Received AssistData without pending classification request"); return; } @@ -791,7 +794,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); if (sVerbose) { - Slog.v(TAG, "New structure for requestId " + requestId + ": " + structure); + Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": " + + structure); } synchronized (mLock) { @@ -1125,6 +1129,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // structure is taken. This causes only one fill request per burst of focus changes. cancelCurrentRequestLocked(); + if (mClassificationState.mHintsToAutofillIdMap == null) { + if (sVerbose) { + Slog.v(TAG, "triggering field classification"); + } + requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION); + } + // Only ask IME to create inline suggestions request when // 1. Autofill provider supports it or client enabled client suggestions. // 2. The render service is available. @@ -1376,7 +1387,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onFillRequestSuccess(int requestId, @Nullable FillResponse response, @NonNull String servicePackageName, int requestFlags) { - final AutofillId[] fieldClassificationIds; final LogMaker requestLog; @@ -1609,10 +1619,68 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Set<Dataset> eligibleDatasets = new ArraySet<>(); Set<AutofillId> eligibleAutofillIds = new ArraySet<>(); for (Dataset dataset : response.getDatasets()) { - if (dataset.getFieldIds() == null) continue; + if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue; if (dataset.getAutofillDatatypes() != null - && dataset.getAutofillDatatypes().size() > 0) { - continue; + && !dataset.getAutofillDatatypes().isEmpty()) { + // This dataset has information relevant for detection too, so we should filter + // them out. It's possible that some fields are applicable to hints only, as such, + // they need to be filtered off. + // TODO(b/266379948): Verify the logic and add tests + // Update dataset to only have non-null fieldValues + + // Figure out if we need to process results. + boolean conversionRequired = false; + int newSize = dataset.getFieldIds().size(); + for (AutofillId id : dataset.getFieldIds()) { + if (id == null) { + conversionRequired = true; + newSize--; + } + } + + if (conversionRequired) { + ArrayList<AutofillId> fieldIds = new ArrayList<>(newSize); + ArrayList<AutofillValue> fieldValues = new ArrayList<>(newSize); + ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(newSize); + ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>(newSize); + ArrayList<InlinePresentation> fieldInlinePresentations = + new ArrayList<>(newSize); + ArrayList<InlinePresentation> fieldInlineTooltipPresentations = + new ArrayList<>(newSize); + ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(newSize); + + for (int i = 0; i < dataset.getFieldIds().size(); i++) { + AutofillId id = dataset.getFieldIds().get(i); + if (id != null) { + // Copy over + fieldIds.add(id); + fieldValues.add(dataset.getFieldValues().get(i)); + fieldPresentations.add(dataset.getFieldPresentation(i)); + fieldDialogPresentations.add(dataset.getFieldDialogPresentation(i)); + fieldInlinePresentations.add(dataset.getFieldInlinePresentation(i)); + fieldInlineTooltipPresentations.add( + dataset.getFieldInlineTooltipPresentation(i)); + fieldFilters.add(dataset.getFilter(i)); + } + } + dataset = + new Dataset( + fieldIds, + fieldValues, + fieldPresentations, + fieldDialogPresentations, + fieldInlinePresentations, + fieldInlineTooltipPresentations, + fieldFilters, + new ArrayList<>(), + dataset.getFieldContent(), + null, + null, + null, + null, + dataset.getId(), + dataset.getAuthentication()); + } } eligibleDatasets.add(dataset); for (AutofillId id : dataset.getFieldIds()) { @@ -1639,6 +1707,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap = mClassificationState.mHintsToAutofillIdMap; + // TODO(266379948): Handle group hints too. ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap = mClassificationState.mGroupHintsToAutofillIdMap; @@ -1649,7 +1718,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState for (int i = 0; i < datasets.size(); i++) { Dataset dataset = datasets.get(i); - if (dataset.getAutofillDatatypes() == null) continue; + if (dataset.getAutofillDatatypes() == null + || dataset.getAutofillDatatypes().isEmpty()) continue; if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue; ArrayList<AutofillId> fieldIds = new ArrayList<>(); @@ -1661,6 +1731,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(); for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) { + if (dataset.getAutofillDatatypes().get(0) == null) continue; String hint = dataset.getAutofillDatatypes().get(j); if (hintsToAutofillIdMap.containsKey(hint)) { @@ -4560,7 +4631,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mResponses == null) { // Set initial capacity as 2 to handle cases where service always requires auth. // TODO: add a metric for number of responses set by server, so we can use its average - // as the initial array capacitiy. + // as the initial array capacity. mResponses = new SparseArray<>(2); } mResponses.put(requestId, newResponse); @@ -4982,6 +5053,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClassificationGroupHintsMap = new ArrayMap<>(); mHintsToAutofillIdMap = new ArrayMap<>(); mGroupHintsToAutofillIdMap = new ArrayMap<>(); + mClassificationCombinedHintsMap = new ArrayMap<>(); Set<android.service.assist.classification.FieldClassification> classifications = response.getClassifications(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c3aea6241f7a..10450f032ffb 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9866,7 +9866,6 @@ public class ActivityManagerService extends IActivityManager.Stub boolean dumpNormalPriority = false; boolean dumpVisibleStacksOnly = false; boolean dumpFocusedStackOnly = false; - boolean dumpVerbose = false; int dumpDisplayId = INVALID_DISPLAY; String dumpPackage = null; int dumpUserId = UserHandle.USER_ALL; @@ -9925,8 +9924,6 @@ public class ActivityManagerService extends IActivityManager.Stub return; } dumpClient = true; - } else if ("--verbose".equals(opt)) { - dumpVerbose = true; } else if ("-h".equals(opt)) { ActivityManagerShellCommand.dumpHelp(pw, true); return; @@ -10213,8 +10210,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else { // Dumping a single activity? if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll, - dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpVerbose, dumpDisplayId, - dumpUserId)) { + dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpDisplayId, dumpUserId)) { ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true); int res = shell.exec(this, null, fd, null, args, null, new ResultReceiver(null)); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 01bb549630b8..809f3f7ff857 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -3957,7 +3957,6 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" --checkin: output checkin format, resetting data."); pw.println(" --C: output checkin format, not resetting data."); pw.println(" --proto: output dump in protocol buffer format."); - pw.println(" --verbose: dumps extra information."); pw.printf(" %s: dump just the DUMPABLE-related state of an activity. Use the %s " + "option to list the supported DUMPABLEs\n", Activity.DUMP_ARG_DUMP_DUMPABLE, Activity.DUMP_ARG_LIST_DUMPABLES); diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 2e3e635c1157..ddc9e9166faa 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -96,6 +96,10 @@ final class CoreSettingsObserver extends ContentObserver { sGlobalSettingToTypeMap.put( Settings.Global.ANGLE_EGL_FEATURES, String.class); sGlobalSettingToTypeMap.put( + Settings.Global.ANGLE_DEFERLIST, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.ANGLE_DEFERLIST_MODE, String.class); + sGlobalSettingToTypeMap.put( Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class); sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class); diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java index dd06464c4699..a028ae16da2f 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java @@ -32,7 +32,7 @@ import java.io.PrintWriter; * Trace.traceEnd. These traces are used for performance testing. */ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServiceInterface { - private static final long TRACE_TAG = Trace.TRACE_TAG_SYSTEM_SERVER; + private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER; private final AppOpsCheckingServiceInterface mService; AppOpsCheckingServiceTracingDecorator( diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 94f12dd7ff01..653b71828c5b 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -1148,7 +1148,7 @@ public class ContextHubService extends IContextHubService.Stub { super.getPreloadedNanoAppIds_enforcePermission(); Objects.requireNonNull(hubInfo, "hubInfo cannot be null"); - long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds(); + long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds(hubInfo.getId()); if (nanoappIds == null) { return new long[0]; } @@ -1261,13 +1261,19 @@ public class ContextHubService extends IContextHubService.Stub { return; } - long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds(); - if (preloadedNanoappIds == null) { - return; - } - for (long preloadedNanoappId : preloadedNanoappIds) { - pw.print("ID: 0x"); - pw.println(Long.toHexString(preloadedNanoappId)); + for (int contextHubId: mContextHubIdToInfoMap.keySet()) { + long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds(contextHubId); + if (preloadedNanoappIds == null) { + return; + } + + pw.print("Context Hub (id="); + pw.print(contextHubId); + pw.println("):"); + for (long preloadedNanoappId : preloadedNanoappIds) { + pw.print(" ID: 0x"); + pw.println(Long.toHexString(preloadedNanoappId)); + } } } diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 1e32ad613eba..eb1a0e27090f 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -363,9 +363,11 @@ public abstract class IContextHubWrapper { * Provides the list of preloaded nanoapp IDs on the system. The output of this API must * not change. * - * @return The list of preloaded nanoapp IDs + * @param contextHubId The context Hub ID. + * + * @return The list of preloaded nanoapp IDs. */ - public abstract long[] getPreloadedNanoappIds(); + public abstract long[] getPreloadedNanoappIds(int contextHubId); /** * Registers a callback with the Context Hub. @@ -714,14 +716,14 @@ public abstract class IContextHubWrapper { } } - public long[] getPreloadedNanoappIds() { + public long[] getPreloadedNanoappIds(int contextHubId) { android.hardware.contexthub.IContextHub hub = getHub(); if (hub == null) { return null; } try { - return hub.getPreloadedNanoappIds(); + return hub.getPreloadedNanoappIds(contextHubId); } catch (RemoteException e) { Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage()); return null; @@ -924,7 +926,7 @@ public abstract class IContextHubWrapper { mHub.queryApps(contextHubId)); } - public long[] getPreloadedNanoappIds() { + public long[] getPreloadedNanoappIds(int contextHubId) { return new long[0]; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6a962625049f..4ec8afde62e8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1247,6 +1247,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: mPowerKeyHandled = true; + // don't actually trigger the shutdown if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + break; + } performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Shut Off"); sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); diff --git a/services/core/java/com/android/server/policy/PowerAction.java b/services/core/java/com/android/server/policy/PowerAction.java index d2de58e96551..deb86b575529 100644 --- a/services/core/java/com/android/server/policy/PowerAction.java +++ b/services/core/java/com/android/server/policy/PowerAction.java @@ -15,6 +15,7 @@ */ package com.android.server.policy; +import android.app.ActivityManager; import android.content.Context; import android.os.UserManager; import com.android.internal.globalactions.LongPressAction; @@ -35,6 +36,11 @@ public final class PowerAction extends SinglePressAction implements LongPressAct @Override public boolean onLongPress() { + // don't actually trigger the reboot if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } UserManager um = mContext.getSystemService(UserManager.class); if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.rebootSafeMode(true); @@ -55,6 +61,11 @@ public final class PowerAction extends SinglePressAction implements LongPressAct @Override public void onPress() { + // don't actually trigger the shutdown if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } // shutdown by making sure radio and power are handled accordingly. mWindowManagerFuncs.shutdown(false /* confirm */); } diff --git a/services/core/java/com/android/server/policy/RestartAction.java b/services/core/java/com/android/server/policy/RestartAction.java index 0f13da82dad3..24c921e4d111 100644 --- a/services/core/java/com/android/server/policy/RestartAction.java +++ b/services/core/java/com/android/server/policy/RestartAction.java @@ -15,6 +15,7 @@ */ package com.android.server.policy; +import android.app.ActivityManager; import android.content.Context; import android.os.UserManager; import com.android.internal.globalactions.LongPressAction; @@ -35,6 +36,11 @@ public final class RestartAction extends SinglePressAction implements LongPressA @Override public boolean onLongPress() { + // don't actually trigger the reboot if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } UserManager um = mContext.getSystemService(UserManager.class); if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { mWindowManagerFuncs.rebootSafeMode(true); @@ -55,6 +61,11 @@ public final class RestartAction extends SinglePressAction implements LongPressA @Override public void onPress() { + // don't actually trigger the reboot if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } mWindowManagerFuncs.reboot(false /* confirm */); } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index c6684dfdb6e0..11294b7c46fc 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2590,7 +2590,7 @@ public final class TvInputManagerService extends SystemService { } @Override - public void notifyAdBuffer( + public void notifyAdBufferReady( IBinder sessionToken, AdBuffer buffer, int userId) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -2602,7 +2602,7 @@ public final class TvInputManagerService extends SystemService { try { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); - getSessionLocked(sessionState).notifyAdBuffer(buffer); + getSessionLocked(sessionState).notifyAdBufferReady(buffer); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in notifyAdBuffer", e); } diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 61c137ed1c5b..bef349f177aa 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -3047,16 +3047,16 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override - public void onAdBuffer(AdBuffer buffer) { + public void onAdBufferReady(AdBuffer buffer) { synchronized (mLock) { if (DEBUG) { - Slogf.d(TAG, "onAdBuffer(buffer=" + buffer + ")"); + Slogf.d(TAG, "onAdBufferReady(buffer=" + buffer + ")"); } if (mSessionState.mSession == null || mSessionState.mClient == null) { return; } try { - mSessionState.mClient.onAdBuffer(buffer, mSessionState.mSeq); + mSessionState.mClient.onAdBufferReady(buffer, mSessionState.mSeq); } catch (RemoteException e) { Slogf.e(TAG, "error in onAdBuffer", e); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 28b974c7330a..424c3271cde6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1734,7 +1734,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A : null; } - private void clearLastParentBeforePip() { + void clearLastParentBeforePip() { if (mLastParentBeforePip != null) { mLastParentBeforePip.mChildPipActivity = null; mLastParentBeforePip = null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 5ea28542207e..bfb735de2d0a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -490,8 +490,7 @@ public abstract class ActivityTaskManagerInternal { /** Dump the current activities state. */ public abstract boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter, - @UserIdInt int userId); + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId); /** Dump the current state for inclusion in oom dump. */ public abstract void dumpForOom(PrintWriter pw); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 6681a32611f1..992743ab8593 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1496,7 +1496,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.persistableMode = ActivityInfo.PERSIST_NEVER; a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; - a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_NO_HISTORY; + a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; a.resizeMode = RESIZE_MODE_UNRESIZEABLE; a.configChanges = 0xffffffff; @@ -4205,8 +4205,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter, - @UserIdInt int userId) { + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) { ArrayList<ActivityRecord> activities; synchronized (mGlobalLock) { @@ -4251,7 +4250,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } } - dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll, dumpVerbose); + dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll); } if (!printedAnything) { // Typically happpens when no task matches displayIdFilter @@ -4265,7 +4264,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * there is a thread associated with the activity. */ private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw, - ActivityRecord r, String[] args, boolean dumpAll, boolean dumpVerbose) { + ActivityRecord r, String[] args, boolean dumpAll) { String innerPrefix = prefix + " "; IApplicationThread appThread = null; synchronized (mGlobalLock) { @@ -4281,15 +4280,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } else { pw.print("(not running)"); } - if (dumpVerbose) { - pw.print(" userId="); - pw.print(r.mUserId); - pw.print(" uid="); - pw.print(r.getUid()); - printDisplayInfoAndNewLine(pw, r); - } else { - pw.println(); - } + pw.print(" userId="); + pw.print(r.mUserId); + pw.print(" uid="); + pw.print(r.getUid()); + printDisplayInfoAndNewLine(pw, r); if (dumpAll) { r.dump(pw, innerPrefix, /* dumpAll= */ true); } @@ -6618,11 +6613,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter, + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) { return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll, - dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, dumpVerbose, displayIdFilter, - userId); + dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId); } @Override diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 7208934efd51..f355f088b608 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1395,9 +1395,9 @@ final class LetterboxUiController { FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null) { + if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any opaque - // activities beneath + // activities beneath or the activity below is embedded which never has letterbox. return; } inheritConfiguration(firstOpaqueActivityBeneath); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3680e6dff9fe..254c911a52da 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2584,6 +2584,9 @@ class Task extends TaskFragment { EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), reason); clearPinnedTaskIfNeed(); + if (mChildPipActivity != null) { + mChildPipActivity.clearLastParentBeforePip(); + } // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 91470f685920..2dc930d42d62 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,7 +24,7 @@ import android.util.Log; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; -import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderMetric; import java.util.Map; @@ -90,33 +90,37 @@ public class MetricUtilities { protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus, Map<String, ProviderSession> providers, int callingUid, ChosenProviderMetric chosenProviderMetric) { - var providerSessions = providers.values(); - int providerSize = providerSessions.size(); - int[] candidateUidList = new int[providerSize]; - int[] candidateQueryRoundTripTimeList = new int[providerSize]; - int[] candidateStatusList = new int[providerSize]; - int index = 0; - for (var session : providerSessions) { - CandidateProviderMetric metric = session.mCandidateProviderMetric; - candidateUidList[index] = metric.getCandidateUid(); - candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds(); - candidateStatusList[index] = metric.getProviderQueryStatus(); - index++; + try { + var providerSessions = providers.values(); + int providerSize = providerSessions.size(); + int[] candidateUidList = new int[providerSize]; + int[] candidateQueryRoundTripTimeList = new int[providerSize]; + int[] candidateStatusList = new int[providerSize]; + int index = 0; + for (var session : providerSessions) { + CandidatePhaseMetric metric = session.mCandidateProviderMetric; + candidateUidList[index] = metric.getCandidateUid(); + candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds(); + candidateStatusList[index] = metric.getProviderQueryStatus(); + index++; + } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED, + /* api_name */apiName.getMetricCode(), + /* caller_uid */ callingUid, + /* api_status */ apiStatus.getMetricCode(), + /* repeated_candidate_provider_uid */ candidateUidList, + /* repeated_candidate_provider_round_trip_time_query_microseconds */ + candidateQueryRoundTripTimeList, + /* repeated_candidate_provider_status */ candidateStatusList, + /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(), + /* chosen_provider_round_trip_time_overall_microseconds */ + chosenProviderMetric.getEntireProviderLatencyMicroseconds(), + /* chosen_provider_final_phase_microseconds (backwards compat only) */ + DEFAULT_INT_32, + /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus()); + } catch (Exception e) { + Log.w(TAG, "Unexpected error during metric logging: " + e); } - FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED, - /* api_name */apiName.getMetricCode(), - /* caller_uid */ callingUid, - /* api_status */ apiStatus.getMetricCode(), - /* repeated_candidate_provider_uid */ candidateUidList, - /* repeated_candidate_provider_round_trip_time_query_microseconds */ - candidateQueryRoundTripTimeList, - /* repeated_candidate_provider_status */ candidateStatusList, - /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(), - /* chosen_provider_round_trip_time_overall_microseconds */ - chosenProviderMetric.getEntireProviderLatencyMicroseconds(), - /* chosen_provider_final_phase_microseconds (backwards compat only) */ - DEFAULT_INT_32, - /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus()); } /** @@ -131,20 +135,24 @@ public class MetricUtilities { */ protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus, int callingUid) { - FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED, - /* api_name */apiName.getMetricCode(), - /* caller_uid */ callingUid, - /* api_status */ apiStatus.getMetricCode(), - /* repeated_candidate_provider_uid */ DEFAULT_REPEATED_INT_32, - /* repeated_candidate_provider_round_trip_time_query_microseconds */ - DEFAULT_REPEATED_INT_32, - /* repeated_candidate_provider_status */ DEFAULT_REPEATED_INT_32, - /* chosen_provider_uid */ DEFAULT_INT_32, - /* chosen_provider_round_trip_time_overall_microseconds */ - DEFAULT_INT_32, - /* chosen_provider_final_phase_microseconds */ - DEFAULT_INT_32, - /* chosen_provider_status */ DEFAULT_INT_32); + try { + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED, + /* api_name */apiName.getMetricCode(), + /* caller_uid */ callingUid, + /* api_status */ apiStatus.getMetricCode(), + /* repeated_candidate_provider_uid */ DEFAULT_REPEATED_INT_32, + /* repeated_candidate_provider_round_trip_time_query_microseconds */ + DEFAULT_REPEATED_INT_32, + /* repeated_candidate_provider_status */ DEFAULT_REPEATED_INT_32, + /* chosen_provider_uid */ DEFAULT_INT_32, + /* chosen_provider_round_trip_time_overall_microseconds */ + DEFAULT_INT_32, + /* chosen_provider_final_phase_microseconds */ + DEFAULT_INT_32, + /* chosen_provider_status */ DEFAULT_INT_32); + } catch (Exception e) { + Log.w(TAG, "Unexpected error during metric logging: " + e); + } } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index a8b9bf6b1fd6..3a72dbc44007 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -31,7 +31,7 @@ import android.os.ICancellationSignal; import android.os.RemoteException; import android.util.Log; -import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.UUID; @@ -59,7 +59,7 @@ public abstract class ProviderSession<T, R> @Nullable protected R mProviderResponse; @NonNull protected Boolean mProviderResponseSet = false; // Specific candidate provider metric for the provider this session handles - @Nullable protected CandidateProviderMetric mCandidateProviderMetric; + @Nullable protected CandidatePhaseMetric mCandidateProviderMetric; @NonNull private int mProviderSessionUid; /** @@ -126,7 +126,7 @@ public abstract class ProviderSession<T, R> mUserId = userId; mComponentName = info.getServiceInfo().getComponentName(); mRemoteCredentialService = remoteCredentialService; - mCandidateProviderMetric = new CandidateProviderMetric(); + mCandidateProviderMetric = new CandidatePhaseMetric(); mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName); } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index c1f35d0f8195..42ec42b170ca 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -36,7 +36,7 @@ import android.util.Log; import com.android.internal.R; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; -import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderMetric; import java.util.ArrayList; @@ -218,7 +218,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan * @param componentName the componentName to associate with a provider */ protected void setChosenMetric(ComponentName componentName) { - CandidateProviderMetric metric = this.mProviders.get(componentName.flattenToString()) + CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString()) .mCandidateProviderMetric; mChosenProviderMetric.setChosenUid(metric.getCandidateUid()); mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime()); diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java new file mode 100644 index 000000000000..37ec8f06c3eb --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java @@ -0,0 +1,75 @@ +/* + * 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.credentials.metrics; + +/** + * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderMetric}. The user is + * shown various entries from the provider responses, and may selectively browse through many + * entries. It is possible that the initial set of browsing is for a provider that is ultimately + * not chosen. This metric will be gathered PER browsing click, and aggregated, so that we can + * understand where user interaction is more cumbersome, informing us for future improvements. This + * can only be complete when the browsing is finished, ending in a final user choice, or possibly + * a cancellation. Thus, this will be collected and emitted in the final phase, though collection + * will begin in the candidate phase when the user begins browsing options. + */ +public class CandidateBrowsingPhaseMetric { + + private static final String TAG = "CandidateSelectionPhaseMetric"; + private static final int SEQUENCE_ID = 3; + // The session id associated with the API Call this candidate provider is a part of, default -1 + private int mSessionId = -1; + // The EntryEnum that was pressed, defaults to -1 (TODO immediately, generate entry enum). + private int mEntryEnum = -1; + // The provider associated with the press, defaults to -1 + private int mProviderUid = -1; + + /* -- The session ID -- */ + + public void setSessionId(int sessionId) { + mSessionId = sessionId; + } + + public int getSessionId() { + return mSessionId; + } + + /* -- The sequence ID -- */ + + public int getSequenceId() { + return SEQUENCE_ID; + } + + /* -- The Entry of this tap -- */ + + public void setEntryEnum(int entryEnum) { + mEntryEnum = entryEnum; + } + + public int getEntryEnum() { + return mEntryEnum; + } + + /* -- The Provider UID of this Tap -- */ + + public void setProviderUid(int providerUid) { + mProviderUid = providerUid; + } + + public int getProviderUid() { + return mProviderUid; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java new file mode 100644 index 000000000000..1c7fb69548fc --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -0,0 +1,246 @@ +/* + * 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.credentials.metrics; + +import android.util.Log; + +import com.android.server.credentials.MetricUtilities; + +/** + * The central candidate provider metric object that mimics our defined metric setup. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). + * TODO(b/270403549) - iterate on this in V3+ + */ +public class CandidatePhaseMetric { + + private static final String TAG = "CandidateProviderMetric"; + // Since this will always be the second in the split sequence, this is statically 2 + private static final int SESSION_ID = 2; + // The sequence number of this emit of the API call, default -1, equal for all candidates + private int mSequenceId = -1; + // Indicates if this provider returned from the query phase, default false + private boolean mQueryReturned = false; + + // The candidate provider uid + private int mCandidateUid = -1; + + // Raw timestamp in nanoseconds, will be converted to microseconds for logging + + //For reference, the initial log timestamp when the service started running the API call + private long mServiceBeganTimeNanoseconds = -1; + // The moment when the query phase began + private long mStartQueryTimeNanoseconds = -1; + // The moment when the query phase ended + private long mQueryFinishTimeNanoseconds = -1; + + // The status of this particular provider + private int mProviderQueryStatus = -1; + // Indicates if an exception was thrown by this provider, false by default + private boolean mHasException = false; + // Indicates the number of total entries available. We can also locally store the entries, but + // cannot emit them in the current split form. TODO(b/271135048) - possibly readjust candidate + // entries. Also, it may be okay to remove this and instead aggregate from inner counts. + // Defaults to -1 + private int mNumEntriesTotal = -1; + // The count of action entries from this provider, defaults to -1 + private int mActionEntryCount = -1; + // The count of credential entries from this provider, defaults to -1 + private int mCredentialEntryCount = -1; + // The *type-count* of the credential entries, defaults to -1 + private int mCredentialEntryTypeCount = -1; + // The count of remote entries from this provider, defaults to -1 + private int mRemoteEntryCount = -1; + // The count of authentication entries from this provider, defaults to -1 + private int mAuthenticationEntryCount = -1; + + public CandidatePhaseMetric() { + } + + /* ---------- Latencies ---------- */ + + /* -- Timestamps -- */ + + public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) { + this.mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds; + } + + public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) { + this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds; + } + + public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) { + this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds; + } + + public long getServiceBeganTimeNanoseconds() { + return this.mServiceBeganTimeNanoseconds; + } + + public long getStartQueryTimeNanoseconds() { + return this.mStartQueryTimeNanoseconds; + } + + public long getQueryFinishTimeNanoseconds() { + return this.mQueryFinishTimeNanoseconds; + } + + /* -- Actual time delta latencies (for local utility) -- */ + + /** + * Returns the latency in microseconds for the query phase. + */ + public int getQueryLatencyMicroseconds() { + return (int) ((this.getQueryFinishTimeNanoseconds() + - this.getStartQueryTimeNanoseconds()) / 1000); + } + + /* --- Time Stamp Conversion to Microseconds from Reference --- */ + + /** + * We collect raw timestamps in nanoseconds for ease of collection. However, given the scope + * of our logging timeframe, and size considerations of the metric, we require these to give us + * the microsecond timestamps from the start reference point. + * + * @param specificTimestamp the timestamp to consider, must be greater than the reference + * @return the microsecond integer timestamp from service start to query began + */ + public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) { + if (specificTimestamp < this.mServiceBeganTimeNanoseconds) { + Log.i(TAG, "The timestamp is before service started, falling back to default int"); + return MetricUtilities.DEFAULT_INT_32; + } + return (int) ((specificTimestamp + - this.mServiceBeganTimeNanoseconds) / 1000); + } + + /* ------------- Provider Query Status ------------ */ + + public void setProviderQueryStatus(int providerQueryStatus) { + this.mProviderQueryStatus = providerQueryStatus; + } + + public int getProviderQueryStatus() { + return this.mProviderQueryStatus; + } + + /* -------------- Candidate Uid ---------------- */ + + public void setCandidateUid(int candidateUid) { + this.mCandidateUid = candidateUid; + } + + public int getCandidateUid() { + return this.mCandidateUid; + } + + /* -------------- Session Id ---------------- */ + public int getSessionId() { + return SESSION_ID; + } + + /* -------------- Sequence Id ---------------- */ + + public void setSequenceId(int sequenceId) { + mSequenceId = sequenceId; + } + + public int getSequenceId() { + return mSequenceId; + } + + /* -------------- Query Returned Status ---------------- */ + + public void setQueryReturned(boolean queryReturned) { + mQueryReturned = queryReturned; + } + + public boolean isQueryReturned() { + return mQueryReturned; + } + + /* -------------- Has Exception Status ---------------- */ + + public void setHasException(boolean hasException) { + mHasException = hasException; + } + + public boolean isHasException() { + return mHasException; + } + + /* -------------- Number of Entries ---------------- */ + + public void setNumEntriesTotal(int numEntriesTotal) { + mNumEntriesTotal = numEntriesTotal; + } + + public int getNumEntriesTotal() { + return mNumEntriesTotal; + } + + /* -------------- Count of Action Entries ---------------- */ + + public void setActionEntryCount(int actionEntryCount) { + mActionEntryCount = actionEntryCount; + } + + public int getActionEntryCount() { + return mActionEntryCount; + } + + /* -------------- Count of Credential Entries ---------------- */ + + public void setCredentialEntryCount(int credentialEntryCount) { + mCredentialEntryCount = credentialEntryCount; + } + + public int getCredentialEntryCount() { + return mCredentialEntryCount; + } + + /* -------------- Count of Credential Entry Types ---------------- */ + + public void setCredentialEntryTypeCount(int credentialEntryTypeCount) { + mCredentialEntryTypeCount = credentialEntryTypeCount; + } + + public int getCredentialEntryTypeCount() { + return mCredentialEntryTypeCount; + } + + /* -------------- Count of Remote Entries ---------------- */ + + public void setRemoteEntryCount(int remoteEntryCount) { + mRemoteEntryCount = remoteEntryCount; + } + + public int getRemoteEntryCount() { + return mRemoteEntryCount; + } + + /* -------------- Count of Authentication Entries ---------------- */ + + public void setAuthenticationEntryCount(int authenticationEntryCount) { + mAuthenticationEntryCount = authenticationEntryCount; + } + + public int getAuthenticationEntryCount() { + return mAuthenticationEntryCount; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java deleted file mode 100644 index 9f438ecc1146..000000000000 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.credentials.metrics; - -/** - * The central candidate provider metric object that mimics our defined metric setup. - * Some types are redundant across these metric collectors, but that has debug use-cases as - * these data-types are available at different moments of the flow (and typically, one can feed - * into the next). - * TODO(b/270403549) - iterate on this in V3+ - */ -public class CandidateProviderMetric { - - private static final String TAG = "CandidateProviderMetric"; - private int mCandidateUid = -1; - - // Raw timestamp in nanoseconds, will be converted to microseconds for logging - - private long mStartQueryTimeNanoseconds = -1; - private long mQueryFinishTimeNanoseconds = -1; - - private int mProviderQueryStatus = -1; - - public CandidateProviderMetric() { - } - - /* ---------- Latencies ---------- */ - - public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) { - this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds; - } - - public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) { - this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds; - } - - public long getStartQueryTimeNanoseconds() { - return this.mStartQueryTimeNanoseconds; - } - - public long getQueryFinishTimeNanoseconds() { - return this.mQueryFinishTimeNanoseconds; - } - - /** - * Returns the latency in microseconds for the query phase. - */ - public int getQueryLatencyMicroseconds() { - return (int) ((this.getQueryFinishTimeNanoseconds() - - this.getStartQueryTimeNanoseconds()) / 1000); - } - - // TODO (in direct next dependent CL, so this is transient) - add reference timestamp in micro - // seconds for this too. - - /* ------------- Provider Query Status ------------ */ - - public void setProviderQueryStatus(int providerQueryStatus) { - this.mProviderQueryStatus = providerQueryStatus; - } - - public int getProviderQueryStatus() { - return this.mProviderQueryStatus; - } - - /* -------------- Candidate Uid ---------------- */ - - public void setCandidateUid(int candidateUid) { - this.mCandidateUid = candidateUid; - } - - public int getCandidateUid() { - return this.mCandidateUid; - } -} diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java index 03102558d21b..1a6109116d38 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java @@ -64,7 +64,7 @@ public class ChosenProviderMetric { /* ---------------- Latencies ------------------ */ - /* ----- Direct Latencies ------- */ + /* ----- Direct Delta Latencies for Local Utility ------- */ /** * In order for a chosen provider to be selected, the call must have successfully begun. @@ -85,7 +85,7 @@ public class ChosenProviderMetric { * metric. * * @param queryPhaseLatencyMicroseconds the millisecond latency for the query phase, typically - * passed in through the {@link CandidateProviderMetric} + * passed in through the {@link CandidatePhaseMetric} */ public void setQueryPhaseLatencyMicroseconds(int queryPhaseLatencyMicroseconds) { mQueryPhaseLatencyMicroseconds = queryPhaseLatencyMicroseconds; @@ -106,7 +106,7 @@ public class ChosenProviderMetric { /** * Returns the full provider (invocation to response) latency in microseconds. Expects the - * start time to be provided, such as from {@link CandidateProviderMetric}. + * start time to be provided, such as from {@link CandidatePhaseMetric}. */ public int getEntireProviderLatencyMicroseconds() { return (int) ((this.mFinalFinishTimeNanoseconds @@ -172,7 +172,7 @@ public class ChosenProviderMetric { return mFinalFinishTimeNanoseconds; } - /* --- Time Stamp Conversion to Microseconds --- */ + /* --- Time Stamp Conversion to Microseconds from Reference Point --- */ /** * We collect raw timestamps in nanoseconds for ease of collection. However, given the scope diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java index c0a994ba340a..685e8d6a3bc5 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java @@ -18,7 +18,8 @@ package com.android.server.location.contexthub; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -75,7 +76,7 @@ public class ContextHubServiceTest { @Test public void testDump_emptyPreloadedNanoappList() { - when(mMockContextHubWrapper.getPreloadedNanoappIds()).thenReturn(null); + when(mMockContextHubWrapper.getPreloadedNanoappIds(anyInt())).thenReturn(null); StringWriter stringWriter = new StringWriter(); ContextHubService service = new ContextHubService(mContext, mMockContextHubWrapper); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 65c71255cd2f..00d7a52752dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -384,6 +384,24 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testNotApplyStrategyToTranslucentActivitiesOverEmbeddedActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Mock the activity as embedded without additional TaskFragment layer in the task for + // simplicity. + doReturn(true).when(mActivity).isEmbedded(); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build(); + doReturn(false).when(translucentActivity).matchParentBounds(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Check the strategy has not being applied + assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + } + + @Test public void testTranslucentActivitiesDontGoInSizeCompactMode() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java index c5387272f0bf..e30206ee0a64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; @Presubmit public class SurfaceSyncGroupTest { private static final String TAG = "SurfaceSyncGroupTest"; + private static final int TIMEOUT_MS = 100; private final Executor mExecutor = Runnable::run; @@ -86,7 +87,7 @@ public class SurfaceSyncGroupTest { syncTarget2.markSyncReady(); - finishedLatch.await(5, TimeUnit.SECONDS); + finishedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch.getCount()); } @@ -124,13 +125,13 @@ public class SurfaceSyncGroupTest { syncTarget1.markSyncReady(); - finishedLatch1.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); assertNotEquals(0, finishedLatch2.getCount()); syncTarget2.markSyncReady(); - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -156,17 +157,17 @@ public class SurfaceSyncGroupTest { // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync // is also done. syncTarget2.markSyncReady(); - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); // Sync did not complete yet assertNotEquals(0, finishedLatch2.getCount()); syncTarget1.markSyncReady(); // The first sync will still get a callback when it's sync requirements are done. - finishedLatch1.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -189,7 +190,7 @@ public class SurfaceSyncGroupTest { syncTarget1.markSyncReady(); // The first sync will still get a callback when it's sync requirements are done. - finishedLatch1.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); syncGroup2.add(syncGroup1, null /* runnable */); @@ -198,7 +199,7 @@ public class SurfaceSyncGroupTest { // Verify that the second sync will receive complete since the merged sync was already // completed before the merge. - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -232,8 +233,8 @@ public class SurfaceSyncGroupTest { syncTarget3.markSyncReady(); // Neither SyncGroup will be ready. - finishedLatch1.await(1, TimeUnit.SECONDS); - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(1, finishedLatch1.getCount()); assertEquals(1, finishedLatch2.getCount()); @@ -241,8 +242,8 @@ public class SurfaceSyncGroupTest { syncTarget2.markSyncReady(); // Both sync groups should be ready after target2 completed. - finishedLatch1.await(5, TimeUnit.SECONDS); - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); assertEquals(0, finishedLatch2.getCount()); } @@ -275,8 +276,8 @@ public class SurfaceSyncGroupTest { syncTarget1.markSyncReady(); // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready. - finishedLatch1.await(1, TimeUnit.SECONDS); - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); assertEquals(1, finishedLatch2.getCount()); @@ -284,7 +285,7 @@ public class SurfaceSyncGroupTest { syncTarget3.markSyncReady(); // SyncGroup2 is finished after target3 completed. - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -357,6 +358,27 @@ public class SurfaceSyncGroupTest { } catch (InterruptedException e) { throw new RuntimeException(e); } + + assertEquals(0, finishedLatch.getCount()); + } + + public void testSurfaceSyncGroupTimeout() throws InterruptedException { + final CountDownLatch finishedLatch = new CountDownLatch(1); + SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG); + syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown); + SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1"); + SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2"); + + syncGroup.add(syncTarget1, null /* runnable */); + syncGroup.add(syncTarget2, null /* runnable */); + syncGroup.markSyncReady(); + + syncTarget1.markSyncReady(); + assertNotEquals(0, finishedLatch.getCount()); + + // Never finish syncTarget2 so it forces the timeout. Timeout is 1 second so wait a little + // over 1 second to make sure it completes. + finishedLatch.await(1100, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch.getCount()); } } diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 788d0e88170d..cf0561db2ae3 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1530,6 +1530,10 @@ public class PhoneNumberUtils { * @return the E.164 representation, or null if the given phone number is not valid. */ public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) { + if (defaultCountryIso != null) { + defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + } + return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.E164); } @@ -1541,6 +1545,10 @@ public class PhoneNumberUtils { * @return the RFC3966 representation, or null if the given phone number is not valid. */ public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) { + if (defaultCountryIso != null) { + defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + } + return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.RFC3966); } @@ -1591,6 +1599,10 @@ public class PhoneNumberUtils { return false; } + if (defaultCountryIso != null) { + defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + } + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); try { PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); @@ -1619,6 +1631,10 @@ public class PhoneNumberUtils { return phoneNumber; } + if (defaultCountryIso != null) { + defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + } + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); String result = null; try { @@ -1671,6 +1687,10 @@ public class PhoneNumberUtils { */ public static String formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso) { + if (defaultCountryIso != null) { + defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + } + int len = phoneNumber.length(); for (int i = 0; i < len; i++) { if (!isDialable(phoneNumber.charAt(i))) { @@ -2900,7 +2920,11 @@ public class PhoneNumberUtils { PhoneNumberUtil util = PhoneNumberUtil.getInstance(); PhoneNumber n1; PhoneNumber n2; - defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + + if (defaultCountryIso != null) { + defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT); + } + try { n1 = util.parseAndKeepRawInput(number1, defaultCountryIso); n2 = util.parseAndKeepRawInput(number2, defaultCountryIso); diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index 684b385d60e8..15fd817ba73b 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -161,9 +161,9 @@ public class SharedConnectivityManager { Resources resources = context.getResources(); try { String servicePackageName = resources.getString( - R.string.shared_connectivity_service_package); + R.string.config_sharedConnectivityServicePackage); String serviceIntentAction = resources.getString( - R.string.shared_connectivity_service_intent_action); + R.string.config_sharedConnectivityServiceIntentAction); return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction); } catch (Resources.NotFoundException e) { Log.e(TAG, "To support shared connectivity service on this device, the service's" @@ -434,14 +434,15 @@ public class SharedConnectivityManager { /** * Gets the list of hotspot networks the user can select to connect to. * - * @return Returns a {@link List} of {@link HotspotNetwork} objects, empty list on failure. + * @return Returns a {@link List} of {@link HotspotNetwork} objects, null on failure. */ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) - @NonNull + @SuppressWarnings("NullableCollection") + @Nullable public List<HotspotNetwork> getHotspotNetworks() { if (mService == null) { - return List.of(); + return null; } try { @@ -449,20 +450,21 @@ public class SharedConnectivityManager { } catch (RemoteException e) { Log.e(TAG, "Exception in getHotspotNetworks", e); } - return List.of(); + return null; } /** * Gets the list of known networks the user can select to connect to. * - * @return Returns a {@link List} of {@link KnownNetwork} objects, empty list on failure. + * @return Returns a {@link List} of {@link KnownNetwork} objects, null on failure. */ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) - @NonNull + @SuppressWarnings("NullableCollection") + @Nullable public List<KnownNetwork> getKnownNetworks() { if (mService == null) { - return List.of(); + return null; } try { @@ -470,7 +472,7 @@ public class SharedConnectivityManager { } catch (RemoteException e) { Log.e(TAG, "Exception in getKnownNetworks", e); } - return List.of(); + return null; } /** diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index c53da9c15d4d..57108e4aa227 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -25,6 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.wifi.sharedconnectivity.app.HotspotNetwork; @@ -40,8 +41,11 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import com.android.internal.R; + import java.util.Collections; import java.util.List; +import java.util.Objects; /** @@ -382,6 +386,30 @@ public abstract class SharedConnectivityService extends Service { } /** + * System and settings UI support on the device for instant tether. + * @return True if the UI can display Instant Tether network data. False otherwise. + */ + public static boolean areHotspotNetworksEnabledForService(@NonNull Context context) { + String servicePackage = context.getResources() + .getString(R.string.config_sharedConnectivityServicePackage); + return Objects.equals(context.getPackageName(), servicePackage) + && context.getResources() + .getBoolean(R.bool.config_hotspotNetworksEnabledForService); + } + + /** + * System and settings UI support on the device for known networks. + * @return True if the UI can display known networks data. False otherwise. + */ + public static boolean areKnownNetworksEnabledForService(@NonNull Context context) { + String servicePackage = context.getResources() + .getString(R.string.config_sharedConnectivityServicePackage); + return Objects.equals(context.getPackageName(), servicePackage) + && context.getResources() + .getBoolean(R.bool.config_knownNetworksEnabledForService); + } + + /** * Implementing application should implement this method. * * Implementation should initiate a connection to the Hotspot Network indicated. 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 8c573e302213..7578dfd11225 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -423,20 +423,20 @@ public class SharedConnectivityManagerTest { * Verify getters. */ @Test - public void getHotspotNetworks_serviceNotConnected_shouldReturnEmptyList() { + public void getHotspotNetworks_serviceNotConnected_shouldReturnNull() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertThat(manager.getKnownNetworks()).isEmpty(); + assertThat(manager.getHotspotNetworks()).isNull(); } @Test - public void getHotspotNetworks_remoteException_shouldReturnEmptyList() throws RemoteException { + public void getHotspotNetworks_remoteException_shouldReturnNull() throws RemoteException { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).getHotspotNetworks(); - assertThat(manager.getKnownNetworks()).isEmpty(); + assertThat(manager.getHotspotNetworks()).isNull(); } @Test @@ -450,21 +450,21 @@ public class SharedConnectivityManagerTest { } @Test - public void getKnownNetworks_serviceNotConnected_shouldReturnEmptyList() + public void getKnownNetworks_serviceNotConnected_shouldReturnNull() throws RemoteException { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertThat(manager.getKnownNetworks()).isEmpty(); + assertThat(manager.getKnownNetworks()).isNull(); } @Test - public void getKnownNetworks_remoteException_shouldReturnEmptyList() throws RemoteException { + public void getKnownNetworks_remoteException_shouldReturnNull() throws RemoteException { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).getKnownNetworks(); - assertThat(manager.getKnownNetworks()).isEmpty(); + assertThat(manager.getKnownNetworks()).isNull(); } @Test 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 19effe5d6f14..b8b6b767eed3 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -26,10 +26,12 @@ import static android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.DEVICE import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.net.wifi.sharedconnectivity.app.HotspotNetwork; import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus; import android.net.wifi.sharedconnectivity.app.KnownNetwork; @@ -86,6 +88,9 @@ public class SharedConnectivityServiceTest { @Mock Context mContext; + @Mock + Resources mResources; + static class FakeSharedConnectivityService extends SharedConnectivityService { public void attachBaseContext(Context context) { super.attachBaseContext(context); @@ -180,6 +185,48 @@ public class SharedConnectivityServiceTest { .isEqualTo(KNOWN_NETWORK_CONNECTION_STATUS); } + @Test + public void areHotspotNetworksEnabledForService() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areHotspotNetworksEnabledForService(mContext)) + .isTrue(); + } + + @Test + public void areHotspotNetworksEnabledForService_notSamePackage_shouldReturnFalse() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("other_package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areHotspotNetworksEnabledForService(mContext)) + .isFalse(); + } + + @Test + public void areKnownNetworksEnabledForService() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isTrue(); + } + + @Test + public void areKnownNetworksEnabledForService_notSamePackage_shouldReturnFalse() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("other_package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isFalse(); + } + private SharedConnectivityService createService() { FakeSharedConnectivityService service = new FakeSharedConnectivityService(); service.attachBaseContext(mContext); |