Merge "Have zygote mount appcompat system properties" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2457e70..372ae6d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -53,6 +53,7 @@
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":aconfig_midi_flags_java_lib{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
@@ -379,6 +380,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media TV
+aconfig_declarations {
+ name: "android.media.tv.flags-aconfig",
+ package: "android.media.tv.flags",
+ srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.media.tv.flags-aconfig-java",
+ aconfig_declarations: "android.media.tv.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media Audio
java_aconfig_library {
name: "com.android.media.audio.flags-aconfig-java",
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 1287cb4..23b36e2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1820,7 +1820,11 @@
jobStatus.getEstimatedNetworkUploadBytes(),
jobStatus.getWorkCount(),
ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())),
- jobStatus.getNamespaceHash());
+ jobStatus.getNamespaceHash(),
+ /* system_measured_source_download_bytes */0,
+ /* system_measured_source_upload_bytes */ 0,
+ /* system_measured_calling_download_bytes */0,
+ /* system_measured_calling_upload_bytes */ 0);
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2257,7 +2261,11 @@
cancelled.getEstimatedNetworkUploadBytes(),
cancelled.getWorkCount(),
ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())),
- cancelled.getNamespaceHash());
+ cancelled.getNamespaceHash(),
+ /* system_measured_source_download_bytes */ 0,
+ /* system_measured_source_upload_bytes */ 0,
+ /* system_measured_calling_download_bytes */0,
+ /* system_measured_calling_upload_bytes */ 0);
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 79653f0..2d49cfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -45,6 +45,7 @@
import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.net.Network;
+import android.net.TrafficStats;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -241,6 +242,14 @@
private int mDeathMarkInternalStopReason;
private String mDeathMarkDebugReason;
+ private long mInitialDownloadedBytesFromSource;
+
+ private long mInitialUploadedBytesFromSource;
+
+ private long mInitialDownloadedBytesFromCalling;
+
+ private long mInitialUploadedBytesFromCalling;
+
// Debugging: reason this job was last stopped.
public String mStoppedReason;
@@ -472,6 +481,14 @@
}
mJobPackageTracker.noteActive(job);
final int sourceUid = job.getSourceUid();
+
+ // Measure UID baseline traffic for deltas
+ mInitialDownloadedBytesFromSource = TrafficStats.getUidRxBytes(sourceUid);
+ mInitialUploadedBytesFromSource = TrafficStats.getUidTxBytes(sourceUid);
+
+ mInitialDownloadedBytesFromCalling = TrafficStats.getUidRxBytes(job.getUid());
+ mInitialUploadedBytesFromCalling = TrafficStats.getUidTxBytes(job.getUid());
+
FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
job.isProxyJob() ? new int[]{sourceUid, job.getUid()} : new int[]{sourceUid},
// Given that the source tag is set by the calling app, it should be connected
@@ -517,7 +534,11 @@
job.getEstimatedNetworkUploadBytes(),
job.getWorkCount(),
ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())),
- job.getNamespaceHash());
+ job.getNamespaceHash(),
+ /* system_measured_source_download_bytes */ 0,
+ /* system_measured_source_upload_bytes */ 0,
+ /* system_measured_calling_download_bytes */ 0,
+ /* system_measured_calling_upload_bytes */ 0);
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1586,7 +1607,15 @@
completedJob.getWorkCount(),
ActivityManager
.processStateAmToProto(mService.getUidProcState(completedJob.getUid())),
- completedJob.getNamespaceHash());
+ completedJob.getNamespaceHash(),
+ TrafficStats.getUidRxBytes(completedJob.getSourceUid())
+ - mInitialDownloadedBytesFromSource,
+ TrafficStats.getUidTxBytes(completedJob.getSourceUid())
+ - mInitialUploadedBytesFromSource,
+ TrafficStats.getUidRxBytes(completedJob.getUid())
+ - mInitialDownloadedBytesFromCalling,
+ TrafficStats.getUidTxBytes(completedJob.getUid())
+ - mInitialUploadedBytesFromCalling);
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/core/api/current.txt b/core/api/current.txt
index 9962469..7e3da22 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -42599,6 +42599,7 @@
field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+ field @FlaggedApi("com.android.server.telecom.flags.add_call_uri_for_missed_calls") public static final String EXTRA_CALL_LOG_URI = "android.telecom.extra.CALL_LOG_URI";
field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telecom.extra.CALL_NETWORK_TYPE";
field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
field public static final String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
@@ -45537,6 +45538,7 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
+ field @FlaggedApi("com.android.internal.telephony.flags.slicing_additional_error_codes") public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5674aec..183b925 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -627,6 +627,7 @@
field public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "android:bind_accessibility_service";
field public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android:capture_consentless_bugreport_on_userdebug_build";
field public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state";
+ field @FlaggedApi("android.view.contentprotection.flags.create_accessibility_overlay_app_op_enabled") public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY = "android:create_accessibility_overlay";
field public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
field public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service";
field public static final String OPSTR_GET_ACCOUNTS = "android:get_accounts";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7e84ceb..b03bd59 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,10 +16,13 @@
package android.app;
+import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
+
import static java.lang.Long.max;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -1495,9 +1498,17 @@
public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
+ /**
+ * Creation of an overlay using accessibility services
+ *
+ * @hide
+ */
+ public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
+ AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 138;
+ public static final int _NUM_OP = 139;
/**
* All app ops represented as strings.
@@ -1641,7 +1652,8 @@
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
+ OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+ OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
})
public @interface AppOpString {}
@@ -2270,6 +2282,16 @@
public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
"android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
+ /**
+ * Creation of an overlay using accessibility services
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED)
+ public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY =
+ "android:create_accessibility_overlay";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2819,7 +2841,11 @@
OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
"RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
.setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
- .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
+ .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
+ new AppOpInfo.Builder(OP_CREATE_ACCESSIBILITY_OVERLAY,
+ OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
+ "CREATE_ACCESSIBILITY_OVERLAY")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 9cf54e3..cc56a1c 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -36,6 +36,7 @@
per-file GameState* = file:/GAME_MANAGER_OWNERS
per-file IGameManager* = file:/GAME_MANAGER_OWNERS
per-file IGameMode* = file:/GAME_MANAGER_OWNERS
+per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 3abe1d9..c6012bb 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -562,13 +562,6 @@
});
}
- private boolean isPostingTaskToBackground(@Nullable RemoteViews views) {
- return Looper.myLooper() == Looper.getMainLooper()
- && RemoteViews.isAdapterConversionEnabled()
- && (mHasPostedLegacyLists = mHasPostedLegacyLists
- || (views != null && views.hasLegacyLists()));
- }
-
/**
* Set the RemoteViews to use for the specified appWidgetIds.
* <p>
@@ -593,16 +586,25 @@
return;
}
- if (isPostingTaskToBackground(views)) {
- createUpdateExecutorIfNull().execute(() -> {
- try {
- mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
- } catch (RemoteException e) {
- Log.e(TAG, "Error updating app widget views in background", e);
- }
- });
+ final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+ && (mHasPostedLegacyLists = mHasPostedLegacyLists
+ || (views != null && views.hasLegacyLists()));
- return;
+ if (isConvertingAdapter) {
+ views.collectAllIntents();
+
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ RemoteViews viewsCopy = new RemoteViews(views);
+ createUpdateExecutorIfNull().execute(() -> {
+ try {
+ mService.updateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating app widget views in background", e);
+ }
+ });
+
+ return;
+ }
}
try {
@@ -714,16 +716,25 @@
return;
}
- if (isPostingTaskToBackground(views)) {
- createUpdateExecutorIfNull().execute(() -> {
- try {
- mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
- } catch (RemoteException e) {
- Log.e(TAG, "Error partially updating app widget views in background", e);
- }
- });
+ final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+ && (mHasPostedLegacyLists = mHasPostedLegacyLists
+ || (views != null && views.hasLegacyLists()));
- return;
+ if (isConvertingAdapter) {
+ views.collectAllIntents();
+
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ RemoteViews viewsCopy = new RemoteViews(views);
+ createUpdateExecutorIfNull().execute(() -> {
+ try {
+ mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error partially updating app widget views in background", e);
+ }
+ });
+
+ return;
+ }
}
try {
@@ -782,16 +793,26 @@
return;
}
- if (isPostingTaskToBackground(views)) {
- createUpdateExecutorIfNull().execute(() -> {
- try {
- mService.updateAppWidgetProvider(provider, views);
- } catch (RemoteException e) {
- Log.e(TAG, "Error updating app widget view using provider in background", e);
- }
- });
+ final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+ && (mHasPostedLegacyLists = mHasPostedLegacyLists
+ || (views != null && views.hasLegacyLists()));
- return;
+ if (isConvertingAdapter) {
+ views.collectAllIntents();
+
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ RemoteViews viewsCopy = new RemoteViews(views);
+ createUpdateExecutorIfNull().execute(() -> {
+ try {
+ mService.updateAppWidgetProvider(provider, viewsCopy);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating app widget view using provider in background",
+ e);
+ }
+ });
+
+ return;
+ }
}
try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ffc4805..ea54c91 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1164,6 +1164,11 @@
* numbers. Applications can <strong>dial</strong> emergency numbers using
* {@link #ACTION_DIAL}, however.
*
+ * <p>Note: This Intent can only be used to dial call forwarding MMI codes if the application
+ * using this intent is set as the default or system dialer. The system will treat any other
+ * application using this Intent for the purpose of dialing call forwarding MMI codes as if the
+ * {@link #ACTION_DIAL} Intent was used instead.
+ *
* <p>Note: An app filling the {@link android.app.role.RoleManager#ROLE_DIALER} role should use
* {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls rather than
* relying on this intent.
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 524afe9..ad3ccc4 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -437,7 +437,14 @@
* Returns {@code true} if the calling application provides a CredentialProviderService that is
* enabled for the current user, or {@code false} otherwise. CredentialProviderServices are
* enabled on a per-service basis so the individual component name of the service should be
- * passed in here.
+ * passed in here. <strong>Usage of this API is discouraged as it is not fully functional, and
+ * may throw a NullPointerException on certain devices and/or API versions.</strong>
+ *
+ * @throws IllegalArgumentException if the componentName package does not match the calling
+ * package name this call will throw an exception
+ *
+ * @throws NullPointerException Usage of this API is discouraged as it is not fully
+ * functional, and may throw a NullPointerException on certain devices and/or API versions
*
* @param componentName the component name to check is enabled
*/
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 52be8f6..127d4a7 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -337,13 +337,13 @@
@VisibleForTesting
public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
- if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+ if (matchCall && !sCallStackDebuggingMatchCall.contains(call.toLowerCase())) {
// Skip if target call doesn't match requested caller
return false;
}
final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
if (matchName && (name == null
- || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+ || !sCallStackDebuggingMatchName.contains(name.toLowerCase()))) {
// Skip if target surface doesn't match requested surface
return false;
}
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index f6ee061..f3dc33c 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -20,3 +20,10 @@
description: "If true, content protection setting ui is displayed in Settings > Privacy & Security > More security & privacy."
bug: "305792348"
}
+
+flag {
+ name: "create_accessibility_overlay_app_op_enabled"
+ namespace: "content_protection"
+ description: "If true, an appop is logged on creation of accessibility overlays."
+ bug: "289081465"
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3078801..403b403 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -85,6 +85,7 @@
import android.util.LongArray;
import android.util.Pair;
import android.util.SizeF;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.util.TypedValue.ComplexDimensionUnit;
@@ -367,6 +368,11 @@
@UnsupportedAppUsage
private BitmapCache mBitmapCache = new BitmapCache();
+ /**
+ * Maps Intent ID to RemoteCollectionItems to avoid duplicate items
+ */
+ private RemoteCollectionCache mCollectionCache = new RemoteCollectionCache();
+
/** Cache of ApplicationInfos used by collection items. */
private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache();
@@ -784,9 +790,12 @@
if (action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
&& itemsAction.mViewId == viewId
&& itemsAction.mServiceIntent != null) {
- mActions.set(i,
- new SetRemoteCollectionItemListAdapterAction(itemsAction.mViewId,
- itemsAction.mServiceIntent));
+ SetRemoteCollectionItemListAdapterAction newCollectionAction =
+ new SetRemoteCollectionItemListAdapterAction(
+ itemsAction.mViewId, itemsAction.mServiceIntent);
+ newCollectionAction.mIntentId = itemsAction.mIntentId;
+ newCollectionAction.mIsReplacedIntoAction = true;
+ mActions.set(i, newCollectionAction);
isActionReplaced = true;
} else if (action instanceof SetRemoteViewsAdapterIntent intentAction
&& intentAction.mViewId == viewId) {
@@ -1048,6 +1057,8 @@
@NonNull
private CompletableFuture<RemoteCollectionItems> mItemsFuture;
final Intent mServiceIntent;
+ int mIntentId = -1;
+ boolean mIsReplacedIntoAction = false;
SetRemoteCollectionItemListAdapterAction(@IdRes int id,
@NonNull RemoteCollectionItems items) {
@@ -1108,38 +1119,36 @@
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
mViewId = parcel.readInt();
- mItemsFuture = CompletableFuture.completedFuture(
- new RemoteCollectionItems(parcel, getHierarchyRootData()));
+ mIntentId = parcel.readInt();
+ mItemsFuture = CompletableFuture.completedFuture(mIntentId != -1
+ ? null
+ : new RemoteCollectionItems(parcel, getHierarchyRootData()));
mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
}
@Override
public void setHierarchyRootData(HierarchyRootData rootData) {
- mItemsFuture = mItemsFuture
- .thenApply(rc -> {
- rc.setHierarchyRootData(rootData);
- return rc;
- });
- }
-
- private static RemoteCollectionItems getCollectionItemsFromFuture(
- CompletableFuture<RemoteCollectionItems> itemsFuture) {
- RemoteCollectionItems items;
- try {
- items = itemsFuture.get();
- } catch (Exception e) {
- Log.e(LOG_TAG, "Error getting collection items from future", e);
- items = new RemoteCollectionItems.Builder().build();
+ if (mIntentId == -1) {
+ mItemsFuture = mItemsFuture
+ .thenApply(rc -> {
+ rc.setHierarchyRootData(rootData);
+ return rc;
+ });
+ return;
}
- return items;
+ // Set the root data for items in the cache instead
+ mCollectionCache.setHierarchyDataForId(mIntentId, rootData);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mViewId);
- RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
- items.writeToParcel(dest, flags, /* attached= */ true);
+ dest.writeInt(mIntentId);
+ if (mIntentId == -1) {
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.writeToParcel(dest, flags, /* attached= */ true);
+ }
dest.writeTypedObject(mServiceIntent, flags);
}
@@ -1149,7 +1158,9 @@
View target = root.findViewById(mViewId);
if (target == null) return;
- RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ RemoteCollectionItems items = mIntentId == -1
+ ? getCollectionItemsFromFuture(mItemsFuture)
+ : mCollectionCache.getItemsForId(mIntentId);
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
@@ -1210,6 +1221,153 @@
}
}
+ private static RemoteCollectionItems getCollectionItemsFromFuture(
+ CompletableFuture<RemoteCollectionItems> itemsFuture) {
+ RemoteCollectionItems items;
+ try {
+ items = itemsFuture.get();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error getting collection items from future", e);
+ items = new RemoteCollectionItems.Builder().build();
+ }
+
+ return items;
+ }
+
+ /**
+ * @hide
+ */
+ public void collectAllIntents() {
+ mCollectionCache.collectAllIntentsNoComplete(this);
+ }
+
+ private class RemoteCollectionCache {
+ private SparseArray<String> mIdToUriMapping = new SparseArray<>();
+ private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
+
+ // We don't put this into the parcel
+ private HashMap<String, CompletableFuture<RemoteCollectionItems>> mTempUriToFutureMapping =
+ new HashMap<>();
+
+ RemoteCollectionCache() { }
+
+ RemoteCollectionCache(RemoteCollectionCache src) {
+ boolean isWaitingCache = src.mTempUriToFutureMapping.size() != 0;
+ for (int i = 0; i < src.mIdToUriMapping.size(); i++) {
+ String uri = src.mIdToUriMapping.valueAt(i);
+ mIdToUriMapping.put(src.mIdToUriMapping.keyAt(i), uri);
+ if (isWaitingCache) {
+ mTempUriToFutureMapping.put(uri, src.mTempUriToFutureMapping.get(uri));
+ } else {
+ mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri));
+ }
+ }
+ }
+
+ RemoteCollectionCache(Parcel in) {
+ int cacheSize = in.readInt();
+ HierarchyRootData currentRootData = new HierarchyRootData(mBitmapCache,
+ this,
+ mApplicationInfoCache,
+ mClassCookies);
+ for (int i = 0; i < cacheSize; i++) {
+ int intentId = in.readInt();
+ String intentUri = in.readString8();
+ RemoteCollectionItems items = new RemoteCollectionItems(in, currentRootData);
+ mIdToUriMapping.put(intentId, intentUri);
+ mUriToCollectionMapping.put(intentUri, items);
+ }
+ }
+
+ void setHierarchyDataForId(int intentId, HierarchyRootData data) {
+ String uri = mIdToUriMapping.get(intentId);
+ if (mTempUriToFutureMapping.get(uri) != null) {
+ CompletableFuture<RemoteCollectionItems> itemsFuture =
+ mTempUriToFutureMapping.get(uri);
+ mTempUriToFutureMapping.put(uri, itemsFuture.thenApply(rc -> {
+ rc.setHierarchyRootData(data);
+ return rc;
+ }));
+
+ return;
+ }
+
+ RemoteCollectionItems items = mUriToCollectionMapping.get(uri);
+ items.setHierarchyRootData(data);
+ }
+
+ RemoteCollectionItems getItemsForId(int intentId) {
+ String uri = mIdToUriMapping.get(intentId);
+ return mUriToCollectionMapping.get(uri);
+ }
+
+ void collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
+ if (inViews.hasSizedRemoteViews()) {
+ for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
+ remoteViews.collectAllIntents();
+ }
+ } else if (inViews.hasLandscapeAndPortraitLayouts()) {
+ inViews.mLandscape.collectAllIntents();
+ inViews.mPortrait.collectAllIntents();
+ } else if (inViews.mActions != null) {
+ for (Action action : inViews.mActions) {
+ if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
+ // Deal with the case where the intent is replaced into the action list
+ if (rca.mIntentId != -1 && !rca.mIsReplacedIntoAction) {
+ continue;
+ }
+
+ if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
+ String uri = mIdToUriMapping.get(rca.mIntentId);
+ mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
+ rca.mItemsFuture = CompletableFuture.completedFuture(null);
+ continue;
+ }
+
+ // Differentiate between the normal collection actions and the ones with
+ // intents.
+ if (rca.mServiceIntent != null) {
+ String uri = rca.mServiceIntent.toUri(0);
+ int index = mIdToUriMapping.indexOfValue(uri);
+ if (index == -1) {
+ int newIntentId = mIdToUriMapping.size();
+ rca.mIntentId = newIntentId;
+ mIdToUriMapping.put(newIntentId, uri);
+ // mUriToIntentMapping.put(uri, mServiceIntent);
+ mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
+ } else {
+ rca.mIntentId = mIdToUriMapping.keyAt(index);
+ }
+ rca.mItemsFuture = CompletableFuture.completedFuture(null);
+ } else {
+ RemoteCollectionItems items = getCollectionItemsFromFuture(
+ rca.mItemsFuture);
+ for (RemoteViews views : items.mViews) {
+ views.collectAllIntents();
+ }
+ }
+ } else if (action instanceof ViewGroupActionAdd vgaa
+ && vgaa.mNestedViews != null) {
+ vgaa.mNestedViews.collectAllIntents();
+ }
+ }
+ }
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mIdToUriMapping.size());
+ for (int i = 0; i < mIdToUriMapping.size(); i++) {
+ out.writeInt(mIdToUriMapping.keyAt(i));
+ String intentUri = mIdToUriMapping.valueAt(i);
+ out.writeString8(intentUri);
+ RemoteCollectionItems items = mTempUriToFutureMapping.get(intentUri) != null
+ ? getCollectionItemsFromFuture(mTempUriToFutureMapping.get(intentUri))
+ : mUriToCollectionMapping.get(intentUri);
+ items.writeToParcel(out, flags, true);
+ }
+ }
+ }
+
private class SetRemoteViewsAdapterIntent extends Action {
Intent mIntent;
boolean mIsAsync = false;
@@ -3850,9 +4008,12 @@
private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) {
if (hierarchyRoot == null) {
mBitmapCache = src.mBitmapCache;
+ // We need to create a new instance because we don't reconstruct collection cache
+ mCollectionCache = new RemoteCollectionCache(src.mCollectionCache);
mApplicationInfoCache = src.mApplicationInfoCache;
} else {
mBitmapCache = hierarchyRoot.mBitmapCache;
+ mCollectionCache = hierarchyRoot.mCollectionCache;
mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
}
if (hierarchyRoot == null || src.mIsRoot) {
@@ -3926,6 +4087,7 @@
mBitmapCache = new BitmapCache(parcel);
// Store the class cookies such that they are available when we clone this RemoteView.
mClassCookies = parcel.copyClassCookies();
+ mCollectionCache = new RemoteCollectionCache(parcel);
} else {
configureAsChild(rootData);
}
@@ -4087,6 +4249,7 @@
private void configureAsChild(@NonNull HierarchyRootData rootData) {
mIsRoot = false;
mBitmapCache = rootData.mBitmapCache;
+ mCollectionCache = rootData.mRemoteCollectionCache;
mApplicationInfoCache = rootData.mApplicationInfoCache;
mClassCookies = rootData.mClassCookies;
configureDescendantsAsChildren();
@@ -6357,6 +6520,7 @@
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags);
}
mApplication.writeToParcel(dest, flags);
if (mIsRoot || mIdealSize == null) {
@@ -6373,6 +6537,7 @@
dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags);
}
dest.writeInt(mSizedRemoteViews.size());
for (RemoteViews view : mSizedRemoteViews) {
@@ -6384,6 +6549,7 @@
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags);
}
mLandscape.writeToParcel(dest, flags);
// Both RemoteViews already share the same package and user
@@ -7262,19 +7428,23 @@
}
private HierarchyRootData getHierarchyRootData() {
- return new HierarchyRootData(mBitmapCache, mApplicationInfoCache, mClassCookies);
+ return new HierarchyRootData(mBitmapCache, mCollectionCache,
+ mApplicationInfoCache, mClassCookies);
}
private static final class HierarchyRootData {
final BitmapCache mBitmapCache;
+ final RemoteCollectionCache mRemoteCollectionCache;
final ApplicationInfoCache mApplicationInfoCache;
final Map<Class, Object> mClassCookies;
HierarchyRootData(
BitmapCache bitmapCache,
+ RemoteCollectionCache remoteCollectionCache,
ApplicationInfoCache applicationInfoCache,
Map<Class, Object> classCookies) {
mBitmapCache = bitmapCache;
+ mRemoteCollectionCache = remoteCollectionCache;
mApplicationInfoCache = applicationInfoCache;
mClassCookies = classCookies;
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 7b7e341..4706dfd 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -19,6 +19,7 @@
import android.os.IBinder;
import android.view.RemoteAnimationDefinition;
import android.window.ITaskFragmentOrganizer;
+import android.window.RemoteTransition;
import android.window.WindowContainerTransaction;
/** @hide */
@@ -65,7 +66,10 @@
/**
* Requests the server to apply the given {@link WindowContainerTransaction}.
+ *
+ * {@link RemoteTransition} can only be used by a system organizer and
+ * {@code shouldApplyIndependently} must be {@code true}. See {@link registerOrganizer}.
*/
void applyTransaction(in WindowContainerTransaction wct, int transitionType,
- boolean shouldApplyIndependently);
+ boolean shouldApplyIndependently, in RemoteTransition remoteTransition);
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index a6c9cec..5c113f8 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -275,7 +275,31 @@
}
wct.setTaskFragmentOrganizer(mInterface);
try {
- getController().applyTransaction(wct, transitionType, shouldApplyIndependently);
+ getController().applyTransaction(
+ wct, transitionType, shouldApplyIndependently, null /* remoteTransition */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Applies a transaction with a {@link RemoteTransition}. Only a system organizer is allowed to
+ * use {@link RemoteTransition}. See {@link TaskFragmentOrganizer#registerOrganizer(boolean)}.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG)
+ public void applySystemTransaction(@NonNull WindowContainerTransaction wct,
+ @TaskFragmentTransitionType int transitionType,
+ @Nullable RemoteTransition remoteTransition) {
+ if (wct.isEmpty()) {
+ return;
+ }
+ wct.setTaskFragmentOrganizer(mInterface);
+ try {
+ getController().applyTransaction(
+ wct, transitionType, remoteTransition != null /* shouldApplyIndependently */,
+ remoteTransition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
new file mode 100644
index 0000000..fa81ee3
--- /dev/null
+++ b/core/java/android/window/flags/OWNERS
@@ -0,0 +1 @@
+per-file responsible_apis.aconfig = file:/BAL_OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 21d1dbb..5d213ca 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -19,11 +19,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.test.AndroidTestCase;
import androidx.test.InstrumentationRegistry;
@@ -31,6 +37,7 @@
import androidx.test.uiautomator.UiDevice;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
System.loadLibrary("powermanagertest_jni");
}
+ // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
/**
* Setup any common data for the upcoming tests.
*/
@@ -454,4 +465,27 @@
parcelBatterySaverPolicyConfigToNativeAndVerify(bs2);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API)
+ public void testBatterySaverSupported_isSupported() throws RemoteException {
+ IPowerManager powerManager = mock(IPowerManager.class);
+ PowerManager pm = new PowerManager(mContext, powerManager,
+ mock(IThermalService.class),
+ Handler.createAsync(Looper.getMainLooper()));
+ when(powerManager.isBatterySaverSupported()).thenReturn(true);
+
+ assertTrue(pm.isBatterySaverSupported());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API)
+ public void testBatterySaverSupported_isNotSupported() throws RemoteException {
+ IPowerManager powerManager = mock(IPowerManager.class);
+ PowerManager pm = new PowerManager(mContext, powerManager,
+ mock(IThermalService.class),
+ Handler.createAsync(Looper.getMainLooper()));
+ when(powerManager.isBatterySaverSupported()).thenReturn(false);
+
+ assertFalse(pm.isBatterySaverSupported());
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 24479d7..a596cef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -353,7 +353,6 @@
closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
// Move the window along the Y axis.
- final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
final float closingTop = (height - closingHeight) * 0.5f;
targetRect.set(
closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9cd318f..723a4a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -40,7 +40,6 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -422,11 +421,6 @@
continue;
}
- // The back gesture has animated this change before transition happen, so here we don't
- // play the animation again.
- if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
- continue;
- }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index b1fc16d..030f601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -44,7 +44,7 @@
private IBinder mTransition = null;
/** The remote to delegate animation to */
- private final RemoteTransition mRemote;
+ private RemoteTransition mRemote;
public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor,
@NonNull RemoteTransition remote) {
@@ -83,6 +83,8 @@
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
});
+ Log.d("b/302551868", "OneShotRemoteHandler#start remote anim null");
+ mRemote = null;
}
};
Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -105,6 +107,8 @@
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
finishCallback.onTransitionFinished(null /* wct */);
+ Log.d("b/302551868", "OneShotRemoteHandler#exception remote anim null");
+ mRemote = null;
}
return true;
}
@@ -123,6 +127,8 @@
// so just assume the worst-case and clear the local transaction.
t.clear();
mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
+ Log.d("b/302551868", "OneShotRemoteHandler#merge remote anim null");
+ mRemote = null;
}
};
try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index baa9aca..c34a14e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -28,6 +28,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -743,6 +744,11 @@
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
}
+ // The change has already animated by back gesture, don't need to play transition
+ // animation on it.
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ info.getChanges().remove(i);
+ }
}
// There does not need animation when:
// A. Transfer starting window. Apply transfer starting window directly if there is no other
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4890ed..ad963dd 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,8 @@
#include "DeferredLayerUpdater.h"
#include "hwui/Paint.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
#include <minikin/Layout.h>
#include <pipeline/skia/SkiaOpenGLPipeline.h>
#include <pipeline/skia/SkiaVulkanPipeline.h>
@@ -179,5 +181,13 @@
return outlineInLocalCoord;
}
+SkFont TestUtils::defaultFont() {
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont =
+ Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+ SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
+ LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
+ return SkFont(sk_ref_sp(skTypeface));
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index ffc664c..0ede902 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -30,6 +30,7 @@
#include <SkBitmap.h>
#include <SkColor.h>
+#include <SkFont.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
@@ -353,6 +354,8 @@
static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
+ static SkFont defaultFont();
+
private:
static std::unordered_map<int, CallCounts> sMockFunctorCounts;
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4a5d946..97d4c82 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -57,7 +57,7 @@
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index bb95490..159541c 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -102,7 +102,7 @@
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
new file mode 100644
index 0000000..a73d1ff
--- /dev/null
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -0,0 +1,8 @@
+package: "android.media.tv.flags"
+
+flag {
+ name: "broadcast_visibility_types"
+ namespace: "media_tv"
+ description: "Constants for standardizing broadcast visibility types."
+ bug: "222402395"
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1088ace..4992ef1 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -290,7 +290,14 @@
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
- session.commit(pendingIntent.getIntentSender());
+ try {
+ session.commit(pendingIntent.getIntentSender());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Cannot install package: ", e);
+ launchFailure(PackageInstaller.STATUS_FAILURE,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ return;
+ }
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
deleted file mode 100644
index 117b48f..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.settingslib.inputmethod;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.text.TextUtils;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settingslib.R;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class InputMethodAndSubtypeEnablerManager implements Preference.OnPreferenceChangeListener {
-
- private final PreferenceFragment mFragment;
-
- private boolean mHaveHardKeyboard;
- private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
- new HashMap<>();
- private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
- private InputMethodManager mImm;
- // TODO: Change mInputMethodInfoList to Map
- private List<InputMethodInfo> mInputMethodInfoList;
- private final Collator mCollator = Collator.getInstance();
-
- public InputMethodAndSubtypeEnablerManager(PreferenceFragment fragment) {
- mFragment = fragment;
- mImm = fragment.getContext().getSystemService(InputMethodManager.class);
-
- mInputMethodInfoList = mImm.getInputMethodList();
- }
-
- public void init(PreferenceFragment fragment, String targetImi, PreferenceScreen root) {
- final Configuration config = fragment.getResources().getConfiguration();
- mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
-
- for (final InputMethodInfo imi : mInputMethodInfoList) {
- // Add subtype preferences of this IME when it is specified or no IME is specified.
- if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
- addInputMethodSubtypePreferences(fragment, imi, root);
- }
- }
- }
-
- public void refresh(Context context, PreferenceFragment fragment) {
- // Refresh internal states in mInputMethodSettingValues to keep the latest
- // "InputMethodInfo"s and "InputMethodSubtype"s
- InputMethodSettingValuesWrapper
- .getInstance(context).refreshAllInputMethodAndSubtypes();
- InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(fragment, context.getContentResolver(),
- mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
- updateAutoSelectionPreferences();
- }
-
- public void save(Context context, PreferenceFragment fragment) {
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(fragment, context.getContentResolver(),
- mInputMethodInfoList, mHaveHardKeyboard);
- }
-
- @Override
- public boolean onPreferenceChange(final Preference pref, final Object newValue) {
- if (!(newValue instanceof Boolean)) {
- return true; // Invoke default behavior.
- }
- final boolean isChecking = (Boolean) newValue;
- for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
- // An auto select subtype preference is changing.
- if (mAutoSelectionPrefsMap.get(imiId) == pref) {
- final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
- autoSelectionPref.setChecked(isChecking);
- // Enable or disable subtypes depending on the auto selection preference.
- setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
- return false;
- }
- }
- // A subtype preference is changing.
- if (pref instanceof InputMethodSubtypePreference) {
- final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
- subtypePref.setChecked(isChecking);
- if (!subtypePref.isChecked()) {
- // It takes care of the case where no subtypes are explicitly enabled then the auto
- // selection preference is going to be checked.
- updateAutoSelectionPreferences();
- }
- return false;
- }
- return true; // Invoke default behavior.
- }
-
- private void addInputMethodSubtypePreferences(PreferenceFragment fragment, InputMethodInfo imi,
- final PreferenceScreen root) {
- Context prefContext = fragment.getPreferenceManager().getContext();
-
- final int subtypeCount = imi.getSubtypeCount();
- if (subtypeCount <= 1) {
- return;
- }
- final String imiId = imi.getId();
- final PreferenceCategory keyboardSettingsCategory =
- new PreferenceCategory(prefContext);
- root.addPreference(keyboardSettingsCategory);
- final PackageManager pm = prefContext.getPackageManager();
- final CharSequence label = imi.loadLabel(pm);
-
- keyboardSettingsCategory.setTitle(label);
- keyboardSettingsCategory.setKey(imiId);
- // TODO: Use toggle Preference if images are ready.
- final TwoStatePreference autoSelectionPref =
- new SwitchWithNoTextPreference(prefContext);
- mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
- keyboardSettingsCategory.addPreference(autoSelectionPref);
- autoSelectionPref.setOnPreferenceChangeListener(this);
-
- final PreferenceCategory activeInputMethodsCategory =
- new PreferenceCategory(prefContext);
- activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
- root.addPreference(activeInputMethodsCategory);
-
- CharSequence autoSubtypeLabel = null;
- final ArrayList<Preference> subtypePreferences = new ArrayList<>();
- for (int index = 0; index < subtypeCount; ++index) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(index);
- if (subtype.overridesImplicitlyEnabledSubtype()) {
- if (autoSubtypeLabel == null) {
- autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
- subtype, prefContext, imi);
- }
- } else {
- final Preference subtypePref = new InputMethodSubtypePreference(
- prefContext, subtype, imi);
- subtypePreferences.add(subtypePref);
- }
- }
- subtypePreferences.sort((lhs, rhs) -> {
- if (lhs instanceof InputMethodSubtypePreference) {
- return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
- }
- return lhs.compareTo(rhs);
- });
- for (final Preference pref : subtypePreferences) {
- activeInputMethodsCategory.addPreference(pref);
- pref.setOnPreferenceChangeListener(this);
- InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
- }
- mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
- if (TextUtils.isEmpty(autoSubtypeLabel)) {
- autoSelectionPref.setTitle(
- R.string.use_system_language_to_select_input_method_subtypes);
- } else {
- autoSelectionPref.setTitle(autoSubtypeLabel);
- }
- }
-
- private boolean isNoSubtypesExplicitlySelected(final String imiId) {
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- for (final Preference pref : subtypePrefs) {
- if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
- return false;
- }
- }
- return true;
- }
-
- private void setAutoSelectionSubtypesEnabled(final String imiId,
- final boolean autoSelectionEnabled) {
- final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
- if (autoSelectionPref == null) {
- return;
- }
- autoSelectionPref.setChecked(autoSelectionEnabled);
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- for (final Preference pref : subtypePrefs) {
- if (pref instanceof TwoStatePreference) {
- // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
- // implicitly checked subtypes. In case of false, all subtype prefs need to be
- // enabled.
- pref.setEnabled(!autoSelectionEnabled);
- if (autoSelectionEnabled) {
- ((TwoStatePreference) pref).setChecked(false);
- }
- }
- }
- if (autoSelectionEnabled) {
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
- mFragment, mFragment.getContext().getContentResolver(),
- mInputMethodInfoList, mHaveHardKeyboard);
- updateImplicitlyEnabledSubtypes(imiId);
- }
- }
-
- private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
- // When targetImiId is null, apply to all subtypes of all IMEs
- for (final InputMethodInfo imi : mInputMethodInfoList) {
- final String imiId = imi.getId();
- final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
- // No need to update implicitly enabled subtypes when the user has unchecked the
- // "subtype auto selection".
- if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
- continue;
- }
- if (imiId.equals(targetImiId) || targetImiId == null) {
- updateImplicitlyEnabledSubtypesOf(imi);
- }
- }
- }
-
- private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
- final String imiId = imi.getId();
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- final List<InputMethodSubtype> implicitlyEnabledSubtypes =
- mImm.getEnabledInputMethodSubtypeList(imi, true);
- if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
- return;
- }
- for (final Preference pref : subtypePrefs) {
- if (!(pref instanceof TwoStatePreference)) {
- continue;
- }
- final TwoStatePreference subtypePref = (TwoStatePreference) pref;
- subtypePref.setChecked(false);
- for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
- final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
- if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
- subtypePref.setChecked(true);
- break;
- }
- }
- }
- }
-
- private void updateAutoSelectionPreferences() {
- for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
- setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
- }
- updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
- }
-}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 17cc9f8..f78811f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -60,7 +60,6 @@
// except for SystemUI-core.
// Copied from compose/features/Android.bp.
static_libs: [
- "CommunalLayoutLib",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 9e06872..21263a9 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -6,7 +6,7 @@
name: "floating_menu_animated_tuck"
namespace: "accessibility"
description: "Sets up animations for tucking/untucking and adjusts clipbounds."
- bug: "24592044"
+ bug: "297556899"
}
flag {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c26d5f5..8eff9bf 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -51,3 +51,11 @@
description: "Enables the scene container framework go/flexiglass."
bug: "283121968"
}
+
+flag {
+ name: "visual_interruptions_refactor"
+ namespace: "systemui"
+ description: "Enables the refactored version of the code to decide when notifications "
+ "HUN, bubble, pulse, or FSI."
+ bug: "261728888"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b8fb264..87a8c35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -4,8 +4,14 @@
import android.os.Bundle
import android.util.SizeF
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -13,16 +19,12 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.res.R
@Composable
fun CommunalHub(
@@ -34,68 +36,91 @@
Box(
modifier = modifier.fillMaxSize().background(Color.White),
) {
- CommunalGridLayout(
- modifier = Modifier.align(Alignment.CenterStart),
- layoutConfig =
- CommunalGridLayoutConfig(
- gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
- gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
- gridHeight = dimensionResource(R.dimen.communal_grid_height),
- gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
- ),
- communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
- )
+ LazyHorizontalGrid(
+ modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
+ rows = GridCells.Fixed(CommunalContentSize.FULL.span),
+ horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+ verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+ ) {
+ if (showTutorial) {
+ items(
+ count = tutorialContentSizes.size,
+ // TODO(b/308148193): a more scalable solution for unique ids.
+ key = { index -> "tutorial_$index" },
+ span = { index -> GridItemSpan(tutorialContentSizes[index].span) },
+ ) { index ->
+ TutorialCard(
+ modifier =
+ Modifier.size(Dimensions.CardWidth, tutorialContentSizes[index].dp()),
+ )
+ }
+ } else {
+ items(
+ count = widgetContent.size,
+ key = { index -> widgetContent[index].id },
+ span = { index -> GridItemSpan(widgetContent[index].size.span) },
+ ) { index ->
+ val widget = widgetContent[index]
+ ContentCard(
+ modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
+ model = widget,
+ )
+ }
+ }
+ }
}
}
-private val tutorialContent =
+// A placeholder for tutorial content.
+@Composable
+private fun TutorialCard(modifier: Modifier = Modifier) {
+ Card(modifier = modifier, content = {})
+}
+
+@Composable
+private fun ContentCard(
+ model: CommunalContentUiModel,
+ modifier: Modifier = Modifier,
+) {
+ AndroidView(
+ modifier = modifier,
+ factory = {
+ model.view.apply {
+ if (this is AppWidgetHostView) {
+ val size = SizeF(Dimensions.CardWidth.value, model.size.dp().value)
+ updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+ }
+ }
+ },
+ )
+}
+
+private fun CommunalContentSize.dp(): Dp {
+ return when (this) {
+ CommunalContentSize.FULL -> Dimensions.CardHeightFull
+ CommunalContentSize.HALF -> Dimensions.CardHeightHalf
+ CommunalContentSize.THIRD -> Dimensions.CardHeightThird
+ }
+}
+
+// Sizes for the tutorial placeholders.
+private val tutorialContentSizes =
listOf(
- tutorialCard(CommunalGridLayoutCard.Size.FULL),
- tutorialCard(CommunalGridLayoutCard.Size.THIRD),
- tutorialCard(CommunalGridLayoutCard.Size.THIRD),
- tutorialCard(CommunalGridLayoutCard.Size.THIRD),
- tutorialCard(CommunalGridLayoutCard.Size.HALF),
- tutorialCard(CommunalGridLayoutCard.Size.HALF),
- tutorialCard(CommunalGridLayoutCard.Size.HALF),
- tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ CommunalContentSize.FULL,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.HALF,
+ CommunalContentSize.HALF,
+ CommunalContentSize.HALF,
+ CommunalContentSize.HALF,
)
-private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
- return object : CommunalGridLayoutCard() {
- override val supportedSizes = listOf(size)
-
- @Composable
- override fun Content(modifier: Modifier, size: SizeF) {
- Card(modifier = modifier, content = {})
- }
- }
-}
-
-private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
- return object : CommunalGridLayoutCard() {
- override val supportedSizes = listOf(convertToCardSize(model.size))
- override val priority = model.priority
-
- @Composable
- override fun Content(modifier: Modifier, size: SizeF) {
- AndroidView(
- modifier = modifier,
- factory = {
- model.view.apply {
- if (this is AppWidgetHostView) {
- updateAppWidgetSize(Bundle(), listOf(size))
- }
- }
- },
- )
- }
- }
-}
-
-private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
- return when (size) {
- CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
- CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
- CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
- }
+private object Dimensions {
+ val CardWidth = 464.dp
+ val CardHeightFull = 630.dp
+ val CardHeightHalf = 307.dp
+ val CardHeightThird = 199.dp
+ val GridHeight = CardHeightFull
+ val Spacing = 16.dp
}
diff --git a/packages/SystemUI/res/layout/volume_dnd_icon.xml b/packages/SystemUI/res/layout/volume_dnd_icon.xml
deleted file mode 100644
index 56587b9..0000000
--- a/packages/SystemUI/res/layout/volume_dnd_icon.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/dnd_icon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginTop="6dp"
- android:layout_marginBottom="6dp">
-
- <ImageView
- android:layout_width="14dp"
- android:layout_height="14dp"
- android:layout_gravity="center"
- android:src="@*android:drawable/ic_qs_dnd"
- android:tint="?android:attr/textColorTertiary"/>
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 85122ba..e69eced 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -18,6 +18,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
import android.animation.Animator;
@@ -63,7 +64,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
@@ -76,8 +76,8 @@
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -280,7 +280,6 @@
// TODO(b/251476085): remove Config and further decompose these properties out of view classes
AuthContainerView(@NonNull Config config,
- @NonNull FeatureFlags featureFlags,
@NonNull CoroutineScope applicationCoroutineScope,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
@@ -295,7 +294,7 @@
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull @Background DelayableExecutor bgExecutor,
@NonNull VibratorHelper vibratorHelper) {
- this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
+ this(config, applicationCoroutineScope, fpProps, faceProps,
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
jankMonitor, promptSelectorInteractor, promptCredentialInteractor, promptViewModel,
credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor,
@@ -304,7 +303,6 @@
@VisibleForTesting
AuthContainerView(@NonNull Config config,
- @NonNull FeatureFlags featureFlags,
@NonNull CoroutineScope applicationCoroutineScope,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
@@ -368,7 +366,7 @@
showPrompt(config, layoutInflater, promptViewModel,
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
- vibratorHelper, featureFlags);
+ vibratorHelper);
// TODO: De-dupe the logic with AuthCredentialPasswordView
setOnKeyListener((v, keyCode, event) -> {
@@ -390,8 +388,7 @@
@NonNull PromptViewModel viewModel,
@Nullable FingerprintSensorPropertiesInternal fpProps,
@Nullable FaceSensorPropertiesInternal faceProps,
- @NonNull VibratorHelper vibratorHelper,
- @NonNull FeatureFlags featureFlags
+ @NonNull VibratorHelper vibratorHelper
) {
if (Utils.isBiometricAllowed(config.mPromptInfo)) {
mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
@@ -407,7 +404,7 @@
getJankListener(view, TRANSIT,
BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
- vibratorHelper, featureFlags);
+ vibratorHelper);
// TODO(b/251476085): migrate these dependencies
if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a64e862..05db56f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -78,7 +78,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.statusbar.CommandQueue;
@@ -120,7 +119,6 @@
private final Handler mHandler;
private final Context mContext;
- private final FeatureFlags mFeatureFlags;
private final Execution mExecution;
private final CommandQueue mCommandQueue;
private final ActivityTaskManager mActivityTaskManager;
@@ -743,7 +741,6 @@
}
@Inject
public AuthController(Context context,
- @NonNull FeatureFlags featureFlags,
@Application CoroutineScope applicationCoroutineScope,
Execution execution,
CommandQueue commandQueue,
@@ -770,7 +767,6 @@
@NonNull UdfpsUtils udfpsUtils,
@NonNull VibratorHelper vibratorHelper) {
mContext = context;
- mFeatureFlags = featureFlags;
mExecution = execution;
mUserManager = userManager;
mLockPatternUtils = lockPatternUtils;
@@ -1316,7 +1312,7 @@
config.mRequestId = requestId;
config.mSensorIds = sensorIds;
config.mScaleProvider = this::getScaleFactor;
- return new AuthContainerView(config, mFeatureFlags, mApplicationCoroutineScope, mFpProps, mFaceProps,
+ return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
mInteractionJankMonitor, mPromptCredentialInteractor, mPromptSelectorInteractor,
viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index ac48b6a..32d9067 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -49,8 +49,6 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
@@ -78,7 +76,6 @@
legacyCallback: Spaghetti.Callback,
applicationScope: CoroutineScope,
vibratorHelper: VibratorHelper,
- featureFlags: FeatureFlags,
): Spaghetti {
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
@@ -380,13 +377,11 @@
}
// Play haptics
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- launch {
- viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
- if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
- vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
- viewModel.clearHaptics()
- }
+ launch {
+ viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+ if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+ vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+ viewModel.clearHaptics()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e49b4a7..647aaf3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,10 +29,7 @@
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -52,9 +49,7 @@
constructor(
displayStateInteractor: DisplayStateInteractor,
promptSelectorInteractor: PromptSelectorInteractor,
- private val vibrator: VibratorHelper,
@Application context: Context,
- private val featureFlags: FeatureFlags,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -339,7 +334,7 @@
_message.value = PromptMessage.Error(message)
if (hapticFeedback) {
- vibrator.error(failedModality)
+ vibrateOnError()
}
messageJob?.cancel()
@@ -457,7 +452,7 @@
_message.value = PromptMessage.Empty
if (!needsUserConfirmation) {
- vibrator.success(modality)
+ vibrateOnSuccess()
}
messageJob?.cancel()
@@ -495,7 +490,7 @@
_isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
- vibrator.success(authState.authenticatedModality)
+ vibrateOnSuccess()
messageJob?.cancel()
messageJob = null
@@ -530,20 +525,12 @@
_forceLargeSize.value = true
}
- private fun VibratorHelper.success(modality: BiometricModality) {
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
- } else {
- vibrateAuthSuccess("$TAG, modality = $modality BP::success")
- }
+ private fun vibrateOnSuccess() {
+ _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
}
- private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- _hapticsToPlay.value = HapticFeedbackConstants.REJECT
- } else {
- vibrateAuthError("$TAG, modality = $modality BP::error")
- }
+ private fun vibrateOnError() {
+ _hapticsToPlay.value = HapticFeedbackConstants.REJECT
}
/** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index f77f989..a79a654 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -75,5 +75,8 @@
/** Indicates an a11y action was made. */
void onA11yAction();
+
+ /** Initialize the class. */
+ void init();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 0dfaf0f..d6b9a11 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -23,6 +23,10 @@
/** */
public class FalsingCollectorFake implements FalsingCollector {
+ @Override
+ public void init() {
+ }
+
@Inject
public FalsingCollectorFake() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index a6b073d..12df96e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -32,13 +32,14 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.sensors.ThresholdSensorEvent;
@@ -65,9 +66,11 @@
private final ProximitySensor mProximitySensor;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
+ private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final BatteryController mBatteryController;
private final DockManager mDockManager;
private final DelayableExecutor mMainExecutor;
+ private final JavaAdapter mJavaAdapter;
private final SystemClock mSystemClock;
private final Lazy<SelectedUserInteractor> mUserInteractor;
@@ -136,10 +139,11 @@
ProximitySensor proximitySensor,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ Lazy<ShadeInteractor> shadeInteractorLazy,
BatteryController batteryController,
DockManager dockManager,
@Main DelayableExecutor mainExecutor,
+ JavaAdapter javaAdapter,
SystemClock systemClock,
Lazy<SelectedUserInteractor> userInteractor) {
mFalsingDataProvider = falsingDataProvider;
@@ -149,12 +153,17 @@
mProximitySensor = proximitySensor;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
+ mShadeInteractorLazy = shadeInteractorLazy;
mBatteryController = batteryController;
mDockManager = dockManager;
mMainExecutor = mainExecutor;
+ mJavaAdapter = javaAdapter;
mSystemClock = systemClock;
mUserInteractor = userInteractor;
+ }
+ @Override
+ public void init() {
mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
@@ -163,7 +172,10 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
- shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+ mJavaAdapter.alwaysCollectFlow(
+ mShadeInteractorLazy.get().isQsExpanded(),
+ this::onQsExpansionChanged
+ );
mBatteryController.addCallback(mBatteryListener);
mDockManager.addListener(mDockEventListener);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index e5b404f..c5d8c79 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -23,6 +23,10 @@
@SysUISingleton
class FalsingCollectorNoOp @Inject constructor() : FalsingCollector {
+ override fun init() {
+ logDebug("NOOP: init")
+ }
+
override fun onSuccessfulUnlock() {
logDebug("NOOP: onSuccessfulUnlock")
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
new file mode 100644
index 0000000..b79538a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.classifier
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Initializes classes related to falsing. */
+@SysUISingleton
+class FalsingCoreStartable @Inject constructor(val falsingCollector: FalsingCollector) :
+ CoreStartable {
+ override fun start() {
+ falsingCollector.init()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index 2729b7b..af467ef 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -19,9 +19,9 @@
import android.content.res.Resources;
import android.view.ViewConfiguration;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.statusbar.phone.NotificationTapHelper;
@@ -37,7 +37,7 @@
import javax.inject.Named;
/** Dagger Module for Falsing. */
-@Module
+@Module(includes = {FalsingStartModule.class})
public interface FalsingModule {
String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers";
String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop";
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt
new file mode 100644
index 0000000..a9f8f37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.classifier
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface FalsingStartModule {
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(FalsingCoreStartable::class)
+ fun bindFalsingCoreStartable(falsingCoreStartable: FalsingCoreStartable?): CoreStartable?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
index b2bc06f..48d3742 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt
@@ -20,4 +20,7 @@
data class SharedNotificationContainerPosition(
val top: Float = 0f,
val bottom: Float = 0f,
+
+ /** Whether any modifications to top/bottom are smoothly animated */
+ val animate: Boolean = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 39a6476..c903709 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -16,14 +16,19 @@
package com.android.systemui.communal.shared.model
-/** Supported sizes for communal content in the layout grid. */
-enum class CommunalContentSize {
+/**
+ * Supported sizes for communal content in the layout grid.
+ *
+ * @param span The span of the content in a column. For example, if FULL is 6, then 3 represents
+ * HALF, 2 represents THIRD, and 1 represents SIXTH.
+ */
+enum class CommunalContentSize(val span: Int) {
/** Content takes the full height of the column. */
- FULL,
+ FULL(6),
/** Content takes half of the height of the column. */
- HALF,
+ HALF(3),
/** Content takes a third of the height of the column. */
- THIRD,
+ THIRD(2),
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
index 98060dc..b60dc2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -9,7 +9,7 @@
* This model stays in the UI layer.
*/
data class CommunalContentUiModel(
+ val id: String,
val view: View,
- val size: CommunalContentSize,
- val priority: Int,
+ val size: CommunalContentSize = CommunalContentSize.HALF,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 25c64ea..390b580 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -20,7 +20,6 @@
import android.content.Context
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
-import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -42,16 +41,16 @@
/** List of widgets to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentUiModel>> =
- communalInteractor.widgetContent.map {
- it.map {
+ communalInteractor.widgetContent.map { widgets ->
+ widgets.map Widget@{ widget ->
// TODO(b/306406256): As adding and removing widgets functionalities are
// supported, cache the host views so they're not recreated each time.
- val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
- return@map CommunalContentUiModel(
+ val hostView =
+ appWidgetHost.createView(context, widget.appWidgetId, widget.providerInfo)
+ return@Widget CommunalContentUiModel(
+ // TODO(b/308148193): a more scalable solution for unique ids.
+ id = "widget_${widget.appWidgetId}",
view = hostView,
- priority = it.priority,
- // All widgets have HALF size.
- size = CommunalContentSize.HALF,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 00d95c0..0f038e1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -37,8 +37,6 @@
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -59,7 +57,6 @@
private val controlsMetricsLogger: ControlsMetricsLogger,
private val vibrator: VibratorHelper,
private val controlsSettingsRepository: ControlsSettingsRepository,
- private val featureFlags: FeatureFlags,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
@@ -123,17 +120,12 @@
}
override fun drag(cvh: ControlViewHolder, isEdge: Boolean) {
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- val constant =
- if (isEdge)
- HapticFeedbackConstants.SEGMENT_TICK
- else
- HapticFeedbackConstants.SEGMENT_FREQUENT_TICK
- vibrator.performHapticFeedback(cvh.layout, constant)
- } else {
- val effect = if (isEdge) Vibrations.rangeEdgeEffect else Vibrations.rangeMiddleEffect
- vibrate(effect)
- }
+ val constant =
+ if (isEdge)
+ HapticFeedbackConstants.SEGMENT_TICK
+ else
+ HapticFeedbackConstants.SEGMENT_FREQUENT_TICK
+ vibrator.performHapticFeedback(cvh.layout, constant)
}
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
deleted file mode 100644
index 29b7e985..0000000
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.controls.ui
-
-import android.os.VibrationEffect
-import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
-
-object Vibrations {
- val rangeEdgeEffect = initRangeEdgeEffect()
- val rangeMiddleEffect = initRangeMiddleEffect()
-
- private fun initRangeEdgeEffect(): VibrationEffect {
- val composition = VibrationEffect.startComposition()
- composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
- return composition.compose()
- }
-
- private fun initRangeMiddleEffect(): VibrationEffect {
- val composition = VibrationEffect.startComposition()
- composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.1f)
- return composition.compose()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f4a9f73..0c364e1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -400,8 +400,7 @@
// 600- status bar
// TODO(b/291315866): Tracking Bug
- @JvmField val SIGNAL_CALLBACK_DEPRECATION =
- unreleasedFlag("signal_callback_deprecation", teamfood = true)
+ @JvmField val SIGNAL_CALLBACK_DEPRECATION = releasedFlag("signal_callback_deprecation")
// TODO(b/301610137): Tracking bug
@JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true)
@@ -414,15 +413,14 @@
val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
// TODO(b/292533677): Tracking Bug
- val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
- unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
+ val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
releasedFlag("incompatible_charging_battery_icon")
// TODO(b/293585143): Tracking Bug
- val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
+ val INSTANT_TETHER = releasedFlag("instant_tether")
// TODO(b/294588085): Tracking Bug
val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 4d5c503..67a12b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -22,6 +22,8 @@
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.internal.jank.InteractionJankMonitor
@@ -242,11 +244,18 @@
}
)
+ view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ viewModel.topInset = insets.getInsetsIgnoringVisibility(insetTypes).top
+ insets
+ }
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
view.removeOnLayoutChangeListener(onLayoutChangeListener)
view.setOnHierarchyChangeListener(null)
+ view.setOnApplyWindowInsetsListener(null)
childViews.clear()
}
}
@@ -288,7 +297,6 @@
oldBottom: Int
) {
val nsslPlaceholder = v.findViewById(R.id.nssl_placeholder) as View?
-
if (nsslPlaceholder != null) {
// After layout, ensure the notifications are positioned correctly
viewModel.onSharedNotificationContainerPositionChanged(
@@ -296,6 +304,11 @@
nsslPlaceholder.bottom.toFloat(),
)
}
+
+ val ksv = v.findViewById(R.id.keyguard_status_view) as View?
+ if (ksv != null) {
+ viewModel.statusViewTop = ksv.top
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 1f98082..e12da53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -65,7 +65,12 @@
*/
private val previewMode = MutableStateFlow(PreviewMode())
- public var clockControllerProvider: Provider<ClockController>? = null
+ var clockControllerProvider: Provider<ClockController>? = null
+
+ /** System insets that keyguard needs to stay out of */
+ var topInset: Int = 0
+ /** Status view top, without translation added in */
+ var statusViewTop: Int = 0
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardState
@@ -102,9 +107,12 @@
scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
)
} else {
+ // Ensure the desired translation doesn't encroach on the top inset
+ val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt()
+ val translationY = -(statusViewTop - Math.max(topInset, statusViewTop + burnInY))
BurnInModel(
translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(),
- translationY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt(),
+ translationY = translationY,
scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
scaleClockOnly = true,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 8103152..13271c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -21,8 +21,8 @@
import android.os.Looper
import android.provider.Settings
import android.view.View
+import android.widget.Switch
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -34,6 +34,7 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.AccessPointController
import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder
import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
@@ -96,6 +97,7 @@
override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) {
state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
+ state.expandedAccessibilityClassName = Switch::class.java.name
model.applyTo(state, mContext)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cc59f87..dfe6adc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1070,7 +1070,6 @@
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener);
mQsController.init();
- mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
@@ -4219,7 +4218,7 @@
return mShadeExpansionStateManager;
}
- private void onQsExpansionChanged(boolean expanded) {
+ void onQsExpansionChanged(boolean expanded) {
updateExpandedHeightToMaxHeight();
setStatusAccessibilityImportance(expanded
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0426388..5114826 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -156,7 +156,6 @@
KeyguardStateController keyguardStateController,
ScreenOffAnimationController screenOffAnimationController,
AuthController authController,
- ShadeExpansionStateManager shadeExpansionStateManager,
Lazy<ShadeInteractor> shadeInteractorLazy,
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor) {
@@ -185,7 +184,6 @@
.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
- shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
float desiredPreferredRefreshRate = context.getResources()
.getInteger(R.integer.config_keyguardRefreshRate);
@@ -303,6 +301,11 @@
mShadeInteractorLazy.get().isAnyExpanded(),
this::onShadeOrQsExpanded
);
+ collectFlow(
+ mWindowRootView,
+ mShadeInteractorLazy.get().isQsExpanded(),
+ this::onQsExpansionChanged
+ );
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index cc46b23..866cfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -26,23 +26,27 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.res.R
+import androidx.lifecycle.lifecycleScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.concurrency.DelayableExecutor
+import kotlinx.coroutines.launch
import java.util.function.Consumer
import javax.inject.Inject
import kotlin.reflect.KMutableProperty0
@@ -56,7 +60,7 @@
private val navigationModeController: NavigationModeController,
private val overviewProxyService: OverviewProxyService,
private val shadeHeaderController: ShadeHeaderController,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val shadeInteractor: ShadeInteractor,
private val fragmentService: FragmentService,
@Main private val delayableExecutor: DelayableExecutor,
private val featureFlags: FeatureFlags,
@@ -65,7 +69,6 @@
private val splitShadeStateController: SplitShadeStateController
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
- private var qsExpanded = false
private var splitShadeEnabled = false
private var isQSDetailShowing = false
private var isQSCustomizing = false
@@ -89,13 +92,6 @@
taskbarVisible = visible
}
}
- private val shadeQsExpansionListener: ShadeQsExpansionListener =
- ShadeQsExpansionListener { isQsExpanded ->
- if (qsExpanded != isQsExpanded) {
- qsExpanded = isQsExpanded
- mView.invalidate()
- }
- }
// With certain configuration changes (like light/dark changes), the nav bar will disappear
// for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value
@@ -122,6 +118,11 @@
}
override fun onInit() {
+ mView.repeatWhenAttached {
+ lifecycleScope.launch {
+ shadeInteractor.isQsExpanded.collect{ _ -> mView.invalidate() }
+ }
+ }
val currentMode: Int = navigationModeController.addListener { mode: Int ->
isGestureNavigation = QuickStepContract.isGesturalMode(mode)
}
@@ -137,7 +138,6 @@
public override fun onViewAttached() {
updateResources()
overviewProxyService.addCallback(taskbarVisibilityListener)
- shadeExpansionStateManager.addQsExpansionListener(shadeQsExpansionListener)
mView.setInsetsChangedListener(delayedInsetSetter)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
mView.setConfigurationChangedListener { updateResources() }
@@ -146,7 +146,6 @@
override fun onViewDetached() {
overviewProxyService.removeCallback(taskbarVisibilityListener)
- shadeExpansionStateManager.removeQsExpansionListener(shadeQsExpansionListener)
mView.removeOnInsetsChangedListener()
mView.removeQSFragmentAttachedListener()
mView.setConfigurationChangedListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 0ec7a36..d73fa14 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -207,12 +207,6 @@
/** pointerId of the pointer we're currently tracking */
private int mTrackingPointer;
- /**
- * Indicates that QS is in expanded state which can happen by:
- * - single pane shade: expanding shade and then expanding QS
- * - split shade: just expanding shade (QS are expanded automatically)
- */
- private boolean mExpanded;
/** Indicates QS is at its max height */
private boolean mFullyExpanded;
/**
@@ -594,9 +588,8 @@
return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
}
-
public boolean getExpanded() {
- return mExpanded;
+ return mShadeRepository.getLegacyIsQsExpanded().getValue();
}
@VisibleForTesting
@@ -613,7 +606,7 @@
// close the whole shade with one motion. Also this will be always true when closing
// split shade as there QS are always expanded so every collapsing motion is motion from
// expanded QS to closed panel
- return mExpandImmediate || (mExpanded
+ return mExpandImmediate || (getExpanded()
&& !isTracking() && !isExpansionAnimating()
&& !mExpansionFromOverscroll);
}
@@ -778,11 +771,11 @@
@VisibleForTesting
void setExpanded(boolean expanded) {
- boolean changed = mExpanded != expanded;
+ boolean changed = getExpanded() != expanded;
if (changed) {
- mExpanded = expanded;
+ mShadeRepository.setLegacyIsQsExpanded(expanded);
updateQsState();
- mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+ mPanelViewControllerLazy.get().onQsExpansionChanged(expanded);
mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
getMinExpansionHeight(), getMaxExpansionHeight(),
mStackScrollerOverscrolling, mAnimatorExpand, mAnimating);
@@ -846,7 +839,7 @@
/** Called when Shade view layout changed. Updates QS expansion or
* starts size change animation if height has changed. */
void handleShadeLayoutChanged(int oldMaxHeight) {
- if (mExpanded && mFullyExpanded) {
+ if (getExpanded() && mFullyExpanded) {
mExpansionHeight = mMaxExpansionHeight;
if (mExpansionHeightSetToMaxListener != null) {
mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
@@ -988,24 +981,24 @@
}
void updateQsState() {
- boolean qsFullScreen = mExpanded && !mSplitShadeEnabled;
+ boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
mNotificationStackScrollLayoutController.setScrollingEnabled(
mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
if (mQsStateUpdateListener != null) {
- mQsStateUpdateListener.onQsStateUpdated(mExpanded, mStackScrollerOverscrolling);
+ mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling);
}
if (mQs == null) return;
- mQs.setExpanded(mExpanded);
+ mQs.setExpanded(getExpanded());
}
/** update expanded state of QS */
public void updateExpansion() {
if (mQs == null) return;
final float squishiness;
- if ((mExpandImmediate || mExpanded) && !mSplitShadeEnabled) {
+ if ((mExpandImmediate || getExpanded()) && !mSplitShadeEnabled) {
squishiness = 1;
} else if (mTransitioningToFullShadeProgress > 0.0f) {
squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction();
@@ -1125,7 +1118,7 @@
mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
/* We also start expanding when flinging closed Qs. Let's exclude that */
&& !mAnimating);
- if (mExpanded) {
+ if (getExpanded()) {
onExpansionStarted();
}
// Since there are QS tiles in the header now, we need to make sure we start listening
@@ -1234,7 +1227,7 @@
Math.min(top / (float) mScrimCornerRadius, 1f));
float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0;
- if (!mExpanded) {
+ if (!getExpanded()) {
bottomRadius = calculateBottomCornerRadius(bottomRadius);
}
mScrimController.setNotificationBottomRadius(bottomRadius);
@@ -1547,7 +1540,7 @@
@VisibleForTesting
void onHeightChanged() {
mMaxExpansionHeight = isQsFragmentCreated() ? mQs.getDesiredHeight() : 0;
- if (mExpanded && mFullyExpanded) {
+ if (getExpanded() && mFullyExpanded) {
mExpansionHeight = mMaxExpansionHeight;
if (mExpansionHeightSetToMaxListener != null) {
mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
@@ -2083,7 +2076,7 @@
ipw.print("mTrackingPointer=");
ipw.println(mTrackingPointer);
ipw.print("mExpanded=");
- ipw.println(mExpanded);
+ ipw.println(getExpanded());
ipw.print("mFullyExpanded=");
ipw.println(mFullyExpanded);
ipw.print("mExpandImmediate=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 9493989..fca98f5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -36,15 +36,12 @@
class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
- private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
private var expanded: Boolean = false
- private var qsExpanded: Boolean = false
- private var qsExpansionFraction = 0f
private var tracking: Boolean = false
private var dragDownPxAmount: Float = 0f
@@ -64,15 +61,6 @@
expansionListeners.remove(listener)
}
- fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
- qsExpansionListeners.add(listener)
- listener.onQsExpansionChanged(qsExpanded)
- }
-
- fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
- qsExpansionListeners.remove(listener)
- }
-
/** Adds a listener that will be notified when the panel state has changed. */
fun addStateListener(listener: ShadeStateListener) {
stateListeners.add(listener)
@@ -153,14 +141,6 @@
expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
}
- /** Called when the quick settings expansion changes to fully expanded or collapsed. */
- fun onQsExpansionChanged(qsExpanded: Boolean) {
- this.qsExpanded = qsExpanded
-
- debugLog("qsExpanded=$qsExpanded")
- qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
- }
-
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 024c8e3..e2e4556 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -83,6 +83,17 @@
val legacyExpandedOrAwaitingInputTransfer: StateFlow<Boolean>
/**
+ * QuickSettingsController.mExpanded as a flow. Indicates that QS is in expanded state:
+ * - single pane shade: expanding shade and then expanding QS
+ * - split shade: just expanding shade (QS are expanded automatically)
+ */
+ @Deprecated("Use ShadeInteractor instead") val legacyIsQsExpanded: StateFlow<Boolean>
+
+ /** Sets whether QS is expanded. */
+ @Deprecated("Use ShadeInteractor instead")
+ fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean)
+
+ /**
* Sets whether the expansion fraction is greater than zero or NPVC is about to accept an input
* transfer from the status bar, home screen, or trackpad.
*/
@@ -175,6 +186,15 @@
override val legacyExpandedOrAwaitingInputTransfer: StateFlow<Boolean> =
_legacyExpandedOrAwaitingInputTransfer.asStateFlow()
+ private val _legacyIsQsExpanded = MutableStateFlow(false)
+ @Deprecated("Use ShadeInteractor instead")
+ override val legacyIsQsExpanded: StateFlow<Boolean> = _legacyIsQsExpanded.asStateFlow()
+
+ @Deprecated("Use ShadeInteractor instead")
+ override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
+ _legacyIsQsExpanded.value = legacyIsQsExpanded
+ }
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyExpandedOrAwaitingInputTransfer(
legacyExpandedOrAwaitingInputTransfer: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index f043c71..a4c4503 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -121,16 +121,37 @@
/**
* The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
- * report 0f.
+ * report 0f. If split shade is enabled, value matches shadeExpansion.
*/
val qsExpansion: StateFlow<Float> =
if (sceneContainerFlags.isEnabled()) {
- sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
+ val qsExp = sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
+ combine(isSplitShadeEnabled, shadeExpansion, qsExp) {
+ isSplitShadeEnabled,
+ shadeExp,
+ qsExp ->
+ if (isSplitShadeEnabled) {
+ shadeExp
+ } else {
+ qsExp
+ }
+ }
.stateIn(scope, SharingStarted.Eagerly, 0f)
} else {
repository.qsExpansion
}
+ /** Whether Quick Settings is expanded a non-zero amount. */
+ val isQsExpanded: StateFlow<Boolean> =
+ if (sceneContainerFlags.isEnabled()) {
+ qsExpansion
+ .map { it > 0 }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
+ } else {
+ repository.legacyIsQsExpanded
+ }
+
/** The amount [0-1] either QS or the shade has been opened. */
val anyExpansion: StateFlow<Float> =
combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 4ea7026..e19fcd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -32,17 +32,15 @@
import com.android.systemui.Dumpable
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,13 +62,11 @@
private val wakeUpCoordinator: NotificationWakeUpCoordinator,
private val bypassController: KeyguardBypassController,
private val headsUpManager: HeadsUpManager,
- private val roundnessManager: NotificationRoundnessManager,
configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
private val falsingManager: FalsingManager,
- shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val shadeInteractor: ShadeInteractor,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
- private val falsingCollector: FalsingCollector,
dumpManager: DumpManager
) : Gefingerpoken, Dumpable {
companion object {
@@ -115,7 +111,6 @@
private val isFalseTouch: Boolean
get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
- var qsExpanded: Boolean = false
var pulseExpandAbortListener: Runnable? = null
var bouncerShowing: Boolean = false
@@ -127,12 +122,6 @@
}
})
- shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
- if (qsExpanded != isQsExpanded) {
- qsExpanded = isQsExpanded
- }
- }
-
mPowerManager = context.getSystemService(PowerManager::class.java)
dumpManager.registerDumpable(this)
}
@@ -148,7 +137,8 @@
}
private fun canHandleMotionEvent(): Boolean {
- return wakeUpCoordinator.canShowPulsingHuns && !qsExpanded && !bouncerShowing
+ return wakeUpCoordinator.canShowPulsingHuns && !shadeInteractor.isQsExpanded.value &&
+ !bouncerShowing
}
private fun startExpansion(event: MotionEvent): Boolean {
@@ -379,7 +369,6 @@
it.println("isExpanding: $isExpanding")
it.println("leavingLockscreen: $leavingLockscreen")
it.println("mPulsing: $mPulsing")
- it.println("qsExpanded: $qsExpanded")
it.println("bouncerShowing: $bouncerShowing")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 5af7cfc..a36d36c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.events
-import androidx.core.animation.Animator
import android.annotation.UiThread
import android.graphics.Point
import android.graphics.Rect
@@ -24,13 +23,15 @@
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
-import com.android.internal.annotations.GuardedBy
-import com.android.systemui.res.R
+import androidx.core.animation.Animator
import com.android.app.animation.Interpolators
+import com.android.internal.annotations.GuardedBy
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
@@ -43,6 +44,8 @@
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -64,11 +67,12 @@
@SysUISingleton
open class PrivacyDotViewController @Inject constructor(
@Main private val mainExecutor: Executor,
+ @Application scope: CoroutineScope,
private val stateController: StatusBarStateController,
private val configurationController: ConfigurationController,
private val contentInsetsProvider: StatusBarContentInsetsProvider,
private val animationScheduler: SystemStatusAnimationScheduler,
- shadeExpansionStateManager: ShadeExpansionStateManager
+ shadeInteractor: ShadeInteractor?
) {
private lateinit var tl: View
private lateinit var tr: View
@@ -135,10 +139,12 @@
}
})
- shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
- dlog("setQsExpanded $isQsExpanded")
- synchronized(lock) {
- nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+ scope.launch {
+ shadeInteractor?.isQsExpanded?.collect { isQsExpanded ->
+ dlog("setQsExpanded $isQsExpanded")
+ synchronized(lock) {
+ nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
new file mode 100644
index 0000000..75e0484
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.statusbar.notification.interruption
+
+import android.database.ContentObserver
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.settings.GlobalSettings
+
+class PeekDisabledSuppressor(
+ private val globalSettings: GlobalSettings,
+ private val headsUpManager: HeadsUpManager,
+ private val logger: NotificationInterruptLogger,
+ @Main private val mainHandler: Handler,
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") {
+ private var isEnabled = false
+
+ override fun shouldSuppress(): Boolean = !isEnabled
+
+ override fun start() {
+ val observer =
+ object : ContentObserver(mainHandler) {
+ override fun onChange(selfChange: Boolean) {
+ val wasEnabled = isEnabled
+
+ isEnabled =
+ globalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF) !=
+ HEADS_UP_OFF
+
+ // QQQ: Do we want to log this even if it hasn't changed?
+ logger.logHeadsUpFeatureChanged(isEnabled)
+
+ // QQQ: Is there a better place for this side effect? What if HeadsUpManager
+ // registered for it directly?
+ if (wasEnabled && !isEnabled) {
+ logger.logWillDismissAll()
+ headsUpManager.releaseAllImmediately()
+ }
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
+ /* notifyForDescendants = */ true,
+ observer
+ )
+
+ // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
+
+ observer.onChange(/* selfChange = */ true)
+ }
+}
+
+class PulseDisabledSuppressor(
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val userTracker: UserTracker,
+) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") {
+ override fun shouldSuppress(): Boolean =
+ !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
+}
+
+class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
+ VisualInterruptionCondition(
+ types = setOf(PULSE),
+ reason = "pulsing disabled by battery saver"
+ ) {
+ override fun shouldSuppress() = batteryController.isAodPowerSave()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 301ddbf..f2ade34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.interruption;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_OFF;
+
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA;
@@ -24,12 +27,10 @@
import android.app.Notification;
import android.app.NotificationManager;
-import android.content.ContentResolver;
import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
-import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import androidx.annotation.NonNull;
@@ -48,6 +49,8 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
import java.util.ArrayList;
import java.util.List;
@@ -66,7 +69,6 @@
private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
- private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatteryController mBatteryController;
@@ -77,6 +79,8 @@
private final UiEventLogger mUiEventLogger;
private final UserTracker mUserTracker;
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final SystemClock mSystemClock;
+ private final GlobalSettings mGlobalSettings;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
@@ -111,7 +115,6 @@
@Inject
public NotificationInterruptStateProviderImpl(
- ContentResolver contentResolver,
PowerManager powerManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
BatteryController batteryController,
@@ -124,8 +127,9 @@
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
UiEventLogger uiEventLogger,
UserTracker userTracker,
- DeviceProvisionedController deviceProvisionedController) {
- mContentResolver = contentResolver;
+ DeviceProvisionedController deviceProvisionedController,
+ SystemClock systemClock,
+ GlobalSettings globalSettings) {
mPowerManager = powerManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -137,15 +141,16 @@
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
mUiEventLogger = uiEventLogger;
mUserTracker = userTracker;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mSystemClock = systemClock;
+ mGlobalSettings = globalSettings;
ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
- boolean wasUsing = mUseHeadsUp;
- mUseHeadsUp = ENABLE_HEADS_UP
- && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
- mContentResolver,
- Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
- Settings.Global.HEADS_UP_OFF);
+ final boolean wasUsing = mUseHeadsUp;
+ final boolean settingEnabled = HEADS_UP_OFF
+ != mGlobalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF);
+ mUseHeadsUp = ENABLE_HEADS_UP && settingEnabled;
mLogger.logHeadsUpFeatureChanged(mUseHeadsUp);
if (wasUsing != mUseHeadsUp) {
if (!mUseHeadsUp) {
@@ -157,16 +162,15 @@
};
if (ENABLE_HEADS_UP) {
- mContentResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+ mGlobalSettings.registerContentObserver(
+ mGlobalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
true,
headsUpObserver);
- mContentResolver.registerContentObserver(
- Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+ mGlobalSettings.registerContentObserver(
+ mGlobalSettings.getUriFor(SETTING_HEADS_UP_TICKER), true,
headsUpObserver);
}
headsUpObserver.onChange(true); // set up
- mDeviceProvisionedController = deviceProvisionedController;
}
@Override
@@ -603,7 +607,7 @@
}
final long when = notification.when;
- final long now = System.currentTimeMillis();
+ final long now = mSystemClock.currentTimeMillis();
final long age = now - when;
if (age < MAX_HUN_WHEN_AGE_MS) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index 920bbe9..da8474e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -51,6 +52,13 @@
}
/**
+ * Initializes the provider.
+ *
+ * Must be called before any method except [addLegacySuppressor].
+ */
+ fun start() {}
+
+ /**
* Adds a [component][suppressor] that can suppress visual interruptions.
*
* This class may call suppressors in any order.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
new file mode 100644
index 0000000..bae7134
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.statusbar.notification.interruption
+
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.os.PowerManager
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+class VisualInterruptionDecisionProviderImpl
+@Inject
+constructor(
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val batteryController: BatteryController,
+ private val globalSettings: GlobalSettings,
+ private val headsUpManager: HeadsUpManager,
+ private val logger: NotificationInterruptLogger,
+ @Main private val mainHandler: Handler,
+ private val powerManager: PowerManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val systemClock: SystemClock,
+ private val userTracker: UserTracker,
+) : VisualInterruptionDecisionProvider {
+ private var started = false
+
+ override fun start() {
+ check(!started)
+
+ addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler))
+ addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker))
+ addCondition(PulseBatterySaverSuppressor(batteryController))
+
+ started = true
+ }
+
+ private class DecisionImpl(
+ override val shouldInterrupt: Boolean,
+ override val logReason: String
+ ) : Decision
+
+ private class FullScreenIntentDecisionImpl(
+ override val shouldInterrupt: Boolean,
+ override val wouldInterruptWithoutDnd: Boolean,
+ override val logReason: String,
+ val originalEntry: NotificationEntry,
+ ) : FullScreenIntentDecision {
+ var hasBeenLogged = false
+ }
+
+ private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
+ private val conditions = mutableListOf<VisualInterruptionCondition>()
+ private val filters = mutableListOf<VisualInterruptionFilter>()
+
+ override fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+ legacySuppressors.add(suppressor)
+ }
+
+ override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+ legacySuppressors.remove(suppressor)
+ }
+
+ fun addCondition(condition: VisualInterruptionCondition) {
+ conditions.add(condition)
+ condition.start()
+ }
+
+ fun addFilter(filter: VisualInterruptionFilter) {
+ filters.add(filter)
+ filter.start()
+ }
+
+ override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
+ check(started)
+ return makeHeadsUpDecision(entry)
+ }
+
+ override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision {
+ check(started)
+ return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) }
+ }
+
+ override fun makeUnloggedFullScreenIntentDecision(
+ entry: NotificationEntry
+ ): FullScreenIntentDecision {
+ check(started)
+ return makeFullScreenDecision(entry)
+ }
+
+ override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
+ check(started)
+ val decisionImpl =
+ decision as? FullScreenIntentDecisionImpl
+ ?: run {
+ Log.wtf(TAG, "Wrong subclass of FullScreenIntentDecision: $decision")
+ return
+ }
+ if (decision.hasBeenLogged) {
+ Log.wtf(TAG, "Already logged decision: $decision")
+ return
+ }
+ logFullScreenIntentDecision(decisionImpl)
+ decision.hasBeenLogged = true
+ }
+
+ override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision {
+ check(started)
+ return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) }
+ }
+
+ private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl {
+ if (statusBarStateController.isDozing) {
+ return makePulseDecision(entry)
+ } else {
+ return makePeekDecision(entry)
+ }
+ }
+
+ private fun makePeekDecision(entry: NotificationEntry): DecisionImpl {
+ checkConditions(PEEK)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkFilters(PEEK, entry)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressInterruptions"
+ )
+ }
+ checkAwakeSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressAwakeInterruptions"
+ )
+ }
+ checkAwakeHeadsUpSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressAwakeHeadsUpInterruptions"
+ )
+ }
+ return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+ }
+
+ private fun makePulseDecision(entry: NotificationEntry): DecisionImpl {
+ checkConditions(PULSE)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkFilters(PULSE, entry)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressInterruptions"
+ )
+ }
+ return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+ }
+
+ private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl {
+ checkConditions(BUBBLE)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkFilters(BUBBLE, entry)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressInterruptions"
+ )
+ }
+ checkAwakeSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressAwakeInterruptions"
+ )
+ }
+ return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+ }
+
+ private fun makeFullScreenDecision(entry: NotificationEntry): FullScreenIntentDecisionImpl {
+ // Not yet implemented.
+ return FullScreenIntentDecisionImpl(
+ shouldInterrupt = true,
+ wouldInterruptWithoutDnd = true,
+ logReason = "FSI logic not yet implemented in VisualInterruptionDecisionProviderImpl",
+ originalEntry = entry
+ )
+ }
+
+ private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) {
+ // Not yet implemented.
+ }
+
+ private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) {
+ // Not yet implemented.
+ }
+
+ private fun logFullScreenIntentDecision(decision: FullScreenIntentDecisionImpl) {
+ // Not yet implemented.
+ }
+
+ private fun checkSuppressors(entry: NotificationEntry) =
+ legacySuppressors.firstOrNull { it.suppressInterruptions(entry) }
+
+ private fun checkAwakeSuppressors(entry: NotificationEntry) =
+ legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) }
+
+ private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) =
+ legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) }
+
+ private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? =
+ conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() }
+
+ private fun checkFilters(
+ type: VisualInterruptionType,
+ entry: NotificationEntry
+ ): VisualInterruptionFilter? =
+ filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+}
+
+private const val TAG = "VisualInterruptionDecisionProviderImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
new file mode 100644
index 0000000..2624363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.statusbar.notification.interruption
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the visual interruptions refactor flag state. */
+object VisualInterruptionRefactor {
+ const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
+
+ /** Whether the refactor is enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.visualInterruptionsRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 4ef80e3..39199df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.statusbar.notification.interruption
import com.android.internal.logging.UiEventLogger.UiEventEnum
@@ -50,6 +51,12 @@
/** An optional UiEvent ID to be recorded when this suppresses an interruption. */
val uiEventId: UiEventEnum?
+
+ /**
+ * Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before
+ * any other methods are called on the suppressor.
+ */
+ fun start() {}
}
/** A reason why visual interruptions might be suppressed regardless of the notification. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 2af7181..6785da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -59,10 +59,8 @@
launch {
viewModel.position.collect {
- controller.updateTopPadding(
- it.top,
- controller.isAddOrRemoveAnimationPending()
- )
+ val animate = it.animate || controller.isAddOrRemoveAnimationPending()
+ controller.updateTopPadding(it.top, animate)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 1229cb9..b86b5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -118,8 +119,15 @@
}
}
} else {
- interactor.topPosition.map { top ->
- keyguardInteractor.sharedNotificationContainerPosition.value.copy(top = top)
+ interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map {
+ (top, qsExpansion) ->
+ // When QS expansion > 0, it should directly set the top padding so do not
+ // animate it
+ val animate = qsExpansion == 0f
+ keyguardInteractor.sharedNotificationContainerPosition.value.copy(
+ top = top,
+ animate = animate
+ )
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f899e2f..5e7b857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -505,7 +505,6 @@
this.authenticators = authenticators
}
},
- featureFlags,
testScope.backgroundScope,
fingerprintProps,
faceProps,
@@ -519,9 +518,7 @@
PromptViewModel(
displayStateInteractor,
promptSelectorInteractor,
- vibrator,
context,
- featureFlags
),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 885abcb..11c5d3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -88,7 +88,6 @@
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
@@ -195,7 +194,6 @@
private Handler mHandler;
private DelayableExecutor mBackgroundExecutor;
private TestableAuthController mAuthController;
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Before
public void setup() throws RemoteException {
@@ -1023,7 +1021,7 @@
private PromptInfo mLastBiometricPromptInfo;
TestableAuthController(Context context) {
- super(context, mFeatureFlags, null /* applicationCoroutineScope */,
+ super(context, null /* applicationCoroutineScope */,
mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
mFingerprintManager, mFaceManager, () -> mUdfpsController,
() -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index b695a0e..d06cbbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -44,18 +44,16 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -64,9 +62,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
@@ -95,7 +91,6 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
private lateinit var iconViewModel: PromptIconViewModel
- private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
@@ -125,10 +120,8 @@
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
- viewModel =
- PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+ viewModel = PromptViewModel(displayStateInteractor, selector, mContext)
iconViewModel = viewModel.iconViewModel
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@Test
@@ -180,26 +173,29 @@
}
@Test
- fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+ fun set_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
- verify(vibrator, if (expectConfirmation) never() else times(1))
- .vibrateAuthSuccess(any())
+ val confirmConstant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(confirmConstant)
+ .isEqualTo(
+ if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
+ else HapticFeedbackConstants.CONFIRM
+ )
if (expectConfirmation) {
viewModel.confirmAuthenticated()
}
- verify(vibrator).vibrateAuthSuccess(any())
- verify(vibrator, never()).vibrateAuthError(any())
+ val confirmedConstant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(confirmedConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
}
@Test
- fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+ fun playSuccessHaptic_SetsConfirmConstant() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
@@ -212,8 +208,7 @@
}
@Test
- fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+ fun playErrorHaptic_SetsRejectConstant() = runGenericTest {
viewModel.showTemporaryError("test", "messageAfterError", false)
val currentConstant by collectLastValue(viewModel.hapticsToPlay)
@@ -251,7 +246,8 @@
DisplayRotation.ROTATION_180 ->
R.raw.biometricprompt_fingerprint_to_error_landscape
DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ R.raw
+ .biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
else -> throw Exception("invalid rotation")
}
assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
@@ -496,7 +492,8 @@
DisplayRotation.ROTATION_180 ->
R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ R.raw
+ .biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
else -> throw Exception("invalid rotation")
}
assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
@@ -733,7 +730,7 @@
}
@Test
- fun plays_haptic_on_errors() = runGenericTest {
+ fun set_haptic_on_errors() = runGenericTest {
viewModel.showTemporaryError(
"so sad",
messageAfterError = "",
@@ -741,8 +738,8 @@
hapticFeedback = true,
)
- verify(vibrator).vibrateAuthError(any())
- verify(vibrator, never()).vibrateAuthSuccess(any())
+ val constant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(constant).isEqualTo(HapticFeedbackConstants.REJECT)
}
@Test
@@ -754,8 +751,8 @@
hapticFeedback = false,
)
- verify(vibrator, never()).vibrateAuthError(any())
- verify(vibrator, never()).vibrateAuthSuccess(any())
+ val constant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(constant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS)
}
private suspend fun TestScope.showTemporaryErrors(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 80fe9e7..fcb18f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -35,13 +36,14 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -54,8 +56,11 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import kotlinx.coroutines.flow.StateFlowKt;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class FalsingCollectorImplTest extends SysuiTestCase {
private FalsingCollectorImpl mFalsingCollector;
@@ -73,7 +78,9 @@
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
- private ShadeExpansionStateManager mShadeExpansionStateManager;
+ private ShadeInteractor mShadeInteractor;
+ @Mock
+ private JavaAdapter mJavaAdapter;
@Mock
private BatteryController mBatteryController;
@Mock
@@ -88,12 +95,16 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
- mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
- mBatteryController,
- mDockManager, mFakeExecutor, mFakeSystemClock, () -> mSelectedUserInteractor);
+ mStatusBarStateController, mKeyguardStateController,
+ () -> mShadeInteractor, mBatteryController,
+ mDockManager, mFakeExecutor,
+ mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor
+ );
+ mFalsingCollector.init();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 8416c46..6a79ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -24,8 +24,6 @@
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -83,8 +81,6 @@
private lateinit var action: ControlActionCoordinatorImpl.Action
private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
- private val featureFlags = FakeFeatureFlags()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -104,7 +100,6 @@
metricsLogger,
vibratorHelper,
controlsSettingsRepository,
- featureFlags
))
coordinator.activityContext = mContext
@@ -200,31 +195,7 @@
}
@Test
- fun drag_isEdge_oneWayHapticsDisabled_usesVibrate() {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-
- coordinator.drag(cvh, true)
-
- verify(vibratorHelper).vibrate(
- Vibrations.rangeEdgeEffect
- )
- }
-
- @Test
- fun drag_isNotEdge_oneWayHapticsDisabled_usesVibrate() {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-
- coordinator.drag(cvh, false)
-
- verify(vibratorHelper).vibrate(
- Vibrations.rangeMiddleEffect
- )
- }
-
- @Test
- fun drag_isEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
-
+ fun drag_isEdge_performsSegmentTickHaptics() {
coordinator.drag(cvh, true)
verify(vibratorHelper).performHapticFeedback(
@@ -234,9 +205,7 @@
}
@Test
- fun drag_isNotEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
-
+ fun drag_isNotEdge_performsFrequentTickHaptics() {
coordinator.drag(cvh, false)
verify(vibratorHelper).performHapticFeedback(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 2b280c0..814a317 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -253,7 +253,6 @@
mKeyguardStateController,
mScreenOffAnimationController,
mAuthController,
- mShadeExpansionStateManager,
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 4f545cb..b80771f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -179,6 +179,8 @@
val translationY by collectLastValue(underTest.translationY)
val scale by collectLastValue(underTest.scale)
+ underTest.statusViewTop = 100
+
// Set to dozing (on AOD)
dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
@@ -200,6 +202,37 @@
}
@Test
+ fun translationAndScaleFromBurnFullyDozingStaysOutOfTopInset() =
+ testScope.runTest {
+ val translationX by collectLastValue(underTest.translationX)
+ val translationY by collectLastValue(underTest.translationY)
+ val scale by collectLastValue(underTest.scale)
+
+ underTest.statusViewTop = 100
+ underTest.topInset = 80
+
+ // Set to dozing (on AOD)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
+ // Trigger a change to the burn-in model
+ burnInFlow.value =
+ BurnInModel(
+ translationX = 20,
+ translationY = -30,
+ scale = 0.5f,
+ )
+ assertThat(translationX).isEqualTo(20)
+ // -20 instead of -30, due to inset of 80
+ assertThat(translationY).isEqualTo(-20)
+ assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+ // Set to the beginning of GONE->AOD transition
+ goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
+ }
+
+ @Test
fun translationAndScaleFromBurnInUseScaleOnly() =
testScope.runTest {
whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 2ce4b04..446a0b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -99,7 +99,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.view.KeyguardRootView;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -148,7 +147,6 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
@@ -335,7 +333,6 @@
@Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
@Mock private JavaAdapter mJavaAdapter;
@Mock private CastController mCastController;
- @Mock private KeyguardRootView mKeyguardRootView;
@Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
@Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
@@ -575,14 +572,13 @@
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
coordinator,
- mKeyguardBypassController, mHeadsUpManager,
- mock(NotificationRoundnessManager.class),
+ mKeyguardBypassController,
+ mHeadsUpManager,
mConfigurationController,
mStatusBarStateController,
mFalsingManager,
- mShadeExpansionStateManager,
+ mShadeInteractor,
mLockscreenShadeTransitionController,
- new FalsingCollectorFake(),
mDumpManager);
when(mKeyguardStatusViewComponentFactory.build(any(), any()))
.thenReturn(mKeyguardStatusViewComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 4ba850c..8e0cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -254,7 +254,6 @@
mKeyguardStateController,
mScreenOffAnimationController,
mAuthController,
- mShadeExpansionStateManager,
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index e70dbc7..778cfa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.testing.TestableResources
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
@@ -27,7 +26,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -38,6 +36,8 @@
import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -46,6 +46,7 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -66,7 +67,7 @@
/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@@ -74,7 +75,7 @@
@Mock lateinit var navigationModeController: NavigationModeController
@Mock lateinit var overviewProxyService: OverviewProxyService
@Mock lateinit var shadeHeaderController: ShadeHeaderController
- @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var fragmentService: FragmentService
@Mock lateinit var fragmentHostManager: FragmentHostManager
@Mock
@@ -88,7 +89,6 @@
lateinit var underTest: NotificationsQSContainerController
- private lateinit var fakeResources: TestableResources
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
@@ -111,6 +111,7 @@
whenever(view.resources).thenReturn(mContext.resources)
whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+ whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
underTest =
NotificationsQSContainerController(
@@ -118,7 +119,7 @@
navigationModeController,
overviewProxyService,
shadeHeaderController,
- shadeExpansionStateManager,
+ shadeInteractor,
fragmentService,
delayableExecutor,
featureFlags,
@@ -475,7 +476,7 @@
navigationModeController,
overviewProxyService,
shadeHeaderController,
- shadeExpansionStateManager,
+ shadeInteractor,
fragmentService,
delayableExecutor,
featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index ac8c924..2342003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.testing.TestableResources
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
@@ -27,7 +26,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -38,6 +36,8 @@
import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -46,6 +46,7 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,7 +66,7 @@
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class NotificationsQSContainerControllerTest : SysuiTestCase() {
@@ -73,7 +74,7 @@
@Mock lateinit var navigationModeController: NavigationModeController
@Mock lateinit var overviewProxyService: OverviewProxyService
@Mock lateinit var shadeHeaderController: ShadeHeaderController
- @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var fragmentService: FragmentService
@Mock lateinit var fragmentHostManager: FragmentHostManager
@Mock
@@ -87,7 +88,6 @@
lateinit var underTest: NotificationsQSContainerController
- private lateinit var fakeResources: TestableResources
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
@@ -111,13 +111,15 @@
whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+ whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
+
underTest =
NotificationsQSContainerController(
view,
navigationModeController,
overviewProxyService,
shadeHeaderController,
- shadeExpansionStateManager,
+ shadeInteractor,
fragmentService,
delayableExecutor,
featureFlags,
@@ -458,7 +460,7 @@
navigationModeController,
overviewProxyService,
shadeHeaderController,
- shadeExpansionStateManager,
+ shadeInteractor,
fragmentService,
delayableExecutor,
featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 6eeafefd..e920687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -189,4 +189,13 @@
underTest.setUdfpsTransitionToFullShadeProgress(1f)
assertThat(underTest.udfpsTransitionToFullShadeProgress.value).isEqualTo(1f)
}
+
+ @Test
+ fun updateLegacyIsQsExpanded() =
+ testScope.runTest {
+ assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(false)
+
+ underTest.setLegacyIsQsExpanded(true)
+ assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 20e5c43..49e5c45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -21,18 +21,17 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,19 +52,18 @@
private val wakeUpCoordinator: NotificationWakeUpCoordinator = mock()
private val bypassController: KeyguardBypassController = mock()
private val headsUpManager: HeadsUpManager = mock()
- private val roundnessManager: NotificationRoundnessManager = mock()
private val configurationController: ConfigurationController = mock()
private val statusBarStateController: StatusBarStateController = mock()
private val falsingManager: FalsingManager = mock()
- private val shadeExpansionStateManager: ShadeExpansionStateManager = mock()
+ private val shadeInteractor: ShadeInteractor = mock()
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
- private val falsingCollector: FalsingCollector = mock()
private val dumpManager: DumpManager = mock()
private val expandableView: ExpandableView = mock()
@Before
fun setUp() {
whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+ whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
pulseExpansionHandler =
PulseExpansionHandler(
@@ -73,13 +71,11 @@
wakeUpCoordinator,
bypassController,
headsUpManager,
- roundnessManager,
configurationController,
statusBarStateController,
falsingManager,
- shadeExpansionStateManager,
+ shadeInteractor,
lockscreenShadeTransitionController,
- falsingCollector,
dumpManager
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 50ce265..1c62161 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.interruption;
+package com.android.systemui.statusbar.notification.interruption;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -27,6 +27,8 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_ON;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -61,9 +63,9 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -74,6 +76,8 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -120,6 +124,8 @@
UserTracker mUserTracker;
@Mock
DeviceProvisionedController mDeviceProvisionedController;
+ FakeSystemClock mSystemClock;
+ FakeGlobalSettings mGlobalSettings;
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
@@ -129,10 +135,12 @@
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
mUiEventLoggerFake = new UiEventLoggerFake();
+ mSystemClock = new FakeSystemClock();
+ mGlobalSettings = new FakeGlobalSettings();
+ mGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
mNotifInterruptionStateProvider =
new NotificationInterruptStateProviderImpl(
- mContext.getContentResolver(),
mPowerManager,
mAmbientDisplayConfiguration,
mBatteryController,
@@ -145,7 +153,9 @@
mKeyguardNotificationVisibilityProvider,
mUiEventLoggerFake,
mUserTracker,
- mDeviceProvisionedController);
+ mDeviceProvisionedController,
+ mSystemClock,
+ mGlobalSettings);
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
@@ -426,7 +436,7 @@
}
private long makeWhenHoursAgo(long hoursAgo) {
- return System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
+ return mSystemClock.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 947bcfb..1d2055e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.statusbar.notification.interruption
import android.testing.AndroidTestingRunner
@@ -19,27 +35,27 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
- override val provider: VisualInterruptionDecisionProvider
- get() =
- NotificationInterruptStateProviderWrapper(
- NotificationInterruptStateProviderImpl(
- context.contentResolver,
- powerManager,
- ambientDisplayConfiguration,
- batteryController,
- statusBarStateController,
- keyguardStateController,
- headsUpManager,
- logger,
- mainHandler,
- flags,
- keyguardNotificationVisibilityProvider,
- uiEventLogger,
- userTracker,
- deviceProvisionedController
- )
- .also { it.mUseHeadsUp = true }
+ override val provider by lazy {
+ NotificationInterruptStateProviderWrapper(
+ NotificationInterruptStateProviderImpl(
+ powerManager,
+ ambientDisplayConfiguration,
+ batteryController,
+ statusBarStateController,
+ keyguardStateController,
+ headsUpManager,
+ logger,
+ mainHandler,
+ flags,
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger,
+ userTracker,
+ deviceProvisionedController,
+ systemClock,
+ globalSettings,
)
+ )
+ }
// Tests of internals of the wrapper:
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
new file mode 100644
index 0000000..ff89bdb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.statusbar.notification.interruption
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
+ override val provider by lazy {
+ VisualInterruptionDecisionProviderImpl(
+ ambientDisplayConfiguration,
+ batteryController,
+ globalSettings,
+ headsUpManager,
+ logger,
+ mainHandler,
+ powerManager,
+ statusBarStateController,
+ systemClock,
+ userTracker,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 6f4bbd5..5511194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.statusbar.notification.interruption
import android.app.ActivityManager
@@ -9,12 +25,16 @@
import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_MUTABLE
+import android.content.Context
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.drawable.Icon
import android.hardware.display.FakeAmbientDisplayConfiguration
-import android.os.Handler
+import android.os.Looper
import android.os.PowerManager
+import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+import android.provider.Settings.Global.HEADS_UP_OFF
+import android.provider.Settings.Global.HEADS_UP_ON
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
@@ -31,8 +51,12 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.leaks.FakeBatteryController
import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.android.systemui.utils.os.FakeHandler
+import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -45,172 +69,399 @@
protected val batteryController = FakeBatteryController(leakCheck)
protected val deviceProvisionedController: DeviceProvisionedController = mock()
protected val flags: NotifPipelineFlags = mock()
+ protected val globalSettings = FakeGlobalSettings()
protected val headsUpManager: HeadsUpManager = mock()
protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
mock()
protected val keyguardStateController: KeyguardStateController = mock()
protected val logger: NotificationInterruptLogger = mock()
- protected val mainHandler: Handler = mock()
+ protected val mainHandler = FakeHandler(Looper.getMainLooper())
protected val powerManager: PowerManager = mock()
protected val statusBarStateController = FakeStatusBarStateController()
+ protected val systemClock = FakeSystemClock()
protected val uiEventLogger = UiEventLoggerFake()
protected val userTracker = FakeUserTracker()
protected abstract val provider: VisualInterruptionDecisionProvider
+ private val neverSuppresses = object : NotificationInterruptSuppressor {}
+
+ private val alwaysSuppressesInterruptions =
+ object : NotificationInterruptSuppressor {
+ override fun suppressInterruptions(entry: NotificationEntry?) = true
+ }
+
+ private val alwaysSuppressesAwakeInterruptions =
+ object : NotificationInterruptSuppressor {
+ override fun suppressAwakeInterruptions(entry: NotificationEntry?) = true
+ }
+
+ private val alwaysSuppressesAwakeHeadsUp =
+ object : NotificationInterruptSuppressor {
+ override fun suppressAwakeHeadsUp(entry: NotificationEntry?) = true
+ }
+
@Before
fun setUp() {
+ globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON)
+
val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
- whenever(headsUpManager.isSnoozed(any())).thenReturn(false)
whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
.thenReturn(false)
+
+ provider.start()
}
@Test
fun testShouldPeek() {
- ensureStateForPeek()
+ ensurePeekState()
+ assertShouldHeadsUp(buildPeekEntry())
+ }
- assertTrue(provider.makeUnloggedHeadsUpDecision(createPeekEntry()).shouldInterrupt)
+ @Test
+ fun testShouldNotPeek_settingDisabled() {
+ ensurePeekState { hunSettingEnabled = false }
+ assertShouldNotHeadsUp(buildPeekEntry())
+ }
+
+ @Test
+ fun testShouldPeek_defaultLegacySuppressor() {
+ ensurePeekState()
+ provider.addLegacySuppressor(neverSuppresses)
+ assertShouldHeadsUp(buildPeekEntry())
+ }
+
+ @Test
+ fun testShouldNotPeek_legacySuppressInterruptions() {
+ ensurePeekState()
+ provider.addLegacySuppressor(alwaysSuppressesInterruptions)
+ assertShouldNotHeadsUp(buildPeekEntry())
+ }
+
+ @Test
+ fun testShouldNotPeek_legacySuppressAwakeInterruptions() {
+ ensurePeekState()
+ provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
+ assertShouldNotHeadsUp(buildPeekEntry())
+ }
+
+ @Test
+ fun testShouldNotPeek_legacySuppressAwakeHeadsUp() {
+ ensurePeekState()
+ provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
+ assertShouldNotHeadsUp(buildPeekEntry())
}
@Test
fun testShouldPulse() {
- ensureStateForPulse()
-
- assertTrue(provider.makeUnloggedHeadsUpDecision(createPulseEntry()).shouldInterrupt)
+ ensurePulseState()
+ assertShouldHeadsUp(buildPulseEntry())
}
@Test
- fun testShouldFsi_awake() {
- ensureStateForAwakeFsi()
-
- assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+ fun testShouldPulse_defaultLegacySuppressor() {
+ ensurePulseState()
+ provider.addLegacySuppressor(neverSuppresses)
+ assertShouldHeadsUp(buildPulseEntry())
}
@Test
- fun testShouldFsi_dreaming() {
- ensureStateForDreamingFsi()
-
- assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+ fun testShouldNotPulse_legacySuppressInterruptions() {
+ ensurePulseState()
+ provider.addLegacySuppressor(alwaysSuppressesInterruptions)
+ assertShouldNotHeadsUp(buildPulseEntry())
}
@Test
- fun testShouldFsi_keyguard() {
- ensureStateForKeyguardFsi()
+ fun testShouldPulse_legacySuppressAwakeInterruptions() {
+ ensurePulseState()
+ provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
+ assertShouldHeadsUp(buildPulseEntry())
+ }
- assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
+ @Test
+ fun testShouldPulse_legacySuppressAwakeHeadsUp() {
+ ensurePulseState()
+ provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
+ assertShouldHeadsUp(buildPulseEntry())
+ }
+
+ @Test
+ fun testShouldNotPulse_disabled() {
+ ensurePulseState { pulseOnNotificationsEnabled = false }
+ assertShouldNotHeadsUp(buildPulseEntry())
+ }
+
+ @Test
+ fun testShouldNotPulse_batterySaver() {
+ ensurePulseState { isAodPowerSave = true }
+ assertShouldNotHeadsUp(buildPulseEntry())
}
@Test
fun testShouldBubble() {
- assertTrue(provider.makeAndLogBubbleDecision(createBubbleEntry()).shouldInterrupt)
+ ensureBubbleState()
+ assertShouldBubble(buildBubbleEntry())
}
- private fun ensureStateForPeek() {
- whenever(powerManager.isScreenOn).thenReturn(true)
- statusBarStateController.dozing = false
- statusBarStateController.dreaming = false
+ @Test
+ fun testShouldBubble_defaultLegacySuppressor() {
+ ensureBubbleState()
+ provider.addLegacySuppressor(neverSuppresses)
+ assertShouldBubble(buildBubbleEntry())
}
- private fun ensureStateForPulse() {
- ambientDisplayConfiguration.fakePulseOnNotificationEnabled = true
- batteryController.setIsAodPowerSave(false)
- statusBarStateController.dozing = true
+ @Test
+ fun testShouldNotBubble_legacySuppressInterruptions() {
+ ensureBubbleState()
+ provider.addLegacySuppressor(alwaysSuppressesInterruptions)
+ assertShouldNotBubble(buildBubbleEntry())
}
- private fun ensureStateForAwakeFsi() {
- whenever(powerManager.isInteractive).thenReturn(false)
- statusBarStateController.dreaming = false
- statusBarStateController.state = SHADE
+ @Test
+ fun testShouldNotBubble_legacySuppressAwakeInterruptions() {
+ ensureBubbleState()
+ provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
+ assertShouldNotBubble(buildBubbleEntry())
}
- private fun ensureStateForDreamingFsi() {
- whenever(powerManager.isInteractive).thenReturn(true)
- statusBarStateController.dreaming = true
- statusBarStateController.state = SHADE
+ @Test
+ fun testShouldBubble_legacySuppressAwakeHeadsUp() {
+ ensureBubbleState()
+ provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
+ assertShouldBubble(buildBubbleEntry())
}
- private fun ensureStateForKeyguardFsi() {
- whenever(powerManager.isInteractive).thenReturn(true)
- statusBarStateController.dreaming = false
- statusBarStateController.state = KEYGUARD
+ @Test
+ fun testShouldFsi_notInteractive() {
+ ensureNotInteractiveFsiState()
+ assertShouldFsi(buildFsiEntry())
}
- private fun createNotif(
- hasFsi: Boolean = false,
- bubbleMetadata: BubbleMetadata? = null
- ): Notification {
- return Notification.Builder(context, TEST_CHANNEL_ID)
- .apply {
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
+ @Test
+ fun testShouldFsi_dreaming() {
+ ensureDreamingFsiState()
+ assertShouldFsi(buildFsiEntry())
+ }
- if (hasFsi) {
- setFullScreenIntent(mock(), /* highPriority = */ true)
- }
+ @Test
+ fun testShouldFsi_keyguard() {
+ ensureKeyguardFsiState()
+ assertShouldFsi(buildFsiEntry())
+ }
- if (bubbleMetadata != null) {
- setBubbleMetadata(bubbleMetadata)
- }
+ private data class State(
+ var hunSettingEnabled: Boolean? = null,
+ var hunSnoozed: Boolean? = null,
+ var isAodPowerSave: Boolean? = null,
+ var isDozing: Boolean? = null,
+ var isDreaming: Boolean? = null,
+ var isInteractive: Boolean? = null,
+ var isScreenOn: Boolean? = null,
+ var keyguardShouldHideNotification: Boolean? = null,
+ var pulseOnNotificationsEnabled: Boolean? = null,
+ var statusBarState: Int? = null,
+ )
+
+ private fun setState(state: State): Unit =
+ state.run {
+ hunSettingEnabled?.let {
+ val newSetting = if (it) HEADS_UP_ON else HEADS_UP_OFF
+ globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, newSetting)
}
- .setContentTitle(TEST_CONTENT_TITLE)
- .setContentText(TEST_CONTENT_TEXT)
- .build()
- }
- private fun createBubbleMetadata(): BubbleMetadata {
- val pendingIntent =
- PendingIntent.getActivity(
- context,
- /* requestCode = */ 0,
- Intent().setPackage(context.packageName),
- FLAG_MUTABLE
- )
+ hunSnoozed?.let { whenever(headsUpManager.isSnoozed(TEST_PACKAGE)).thenReturn(it) }
- val icon = Icon.createWithResource(context.resources, R.drawable.android)
+ isAodPowerSave?.let { batteryController.setIsAodPowerSave(it) }
- return BubbleMetadata.Builder(pendingIntent, icon).build()
- }
+ isDozing?.let { statusBarStateController.dozing = it }
- private fun createEntry(
- notif: Notification,
- importance: Int = IMPORTANCE_DEFAULT,
- canBubble: Boolean? = null
- ): NotificationEntry {
- return NotificationEntryBuilder()
- .apply {
- setPkg(TEST_PACKAGE)
- setOpPkg(TEST_PACKAGE)
- setTag(TEST_TAG)
- setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
- setNotification(notif)
- setImportance(importance)
+ isDreaming?.let { statusBarStateController.dreaming = it }
- if (canBubble != null) {
- setCanBubble(canBubble)
- }
+ isInteractive?.let { whenever(powerManager.isInteractive).thenReturn(it) }
+
+ isScreenOn?.let { whenever(powerManager.isScreenOn).thenReturn(it) }
+
+ keyguardShouldHideNotification?.let {
+ whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
+ .thenReturn(it)
}
- .build()
- }
- private fun createPeekEntry() = createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH)
+ pulseOnNotificationsEnabled?.let {
+ ambientDisplayConfiguration.fakePulseOnNotificationEnabled = it
+ }
- private fun createPulseEntry() =
- createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH).also {
- modifyRanking(it).setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()
+ statusBarState?.let { statusBarStateController.state = it }
}
- private fun createFsiEntry() =
- createEntry(notif = createNotif(hasFsi = true), importance = IMPORTANCE_HIGH)
+ private fun ensureState(block: State.() -> Unit) =
+ State()
+ .apply {
+ keyguardShouldHideNotification = false
+ apply(block)
+ }
+ .run(this::setState)
- private fun createBubbleEntry() =
- createEntry(
- notif = createNotif(bubbleMetadata = createBubbleMetadata()),
- importance = IMPORTANCE_HIGH,
- canBubble = true
- )
+ private fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
+ hunSettingEnabled = true
+ hunSnoozed = false
+ isDozing = false
+ isDreaming = false
+ isScreenOn = true
+ run(block)
+ }
+
+ private fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
+ isAodPowerSave = false
+ isDozing = true
+ pulseOnNotificationsEnabled = true
+ run(block)
+ }
+
+ private fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)
+
+ private fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
+ isDreaming = false
+ isInteractive = false
+ statusBarState = SHADE
+ run(block)
+ }
+
+ private fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
+ isDreaming = true
+ isInteractive = true
+ statusBarState = SHADE
+ run(block)
+ }
+
+ private fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
+ isDreaming = false
+ isInteractive = true
+ statusBarState = KEYGUARD
+ run(block)
+ }
+
+ private fun assertShouldHeadsUp(entry: NotificationEntry) =
+ provider.makeUnloggedHeadsUpDecision(entry).let {
+ assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
+ }
+
+ private fun assertShouldNotHeadsUp(entry: NotificationEntry) =
+ provider.makeUnloggedHeadsUpDecision(entry).let {
+ assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
+ }
+
+ private fun assertShouldBubble(entry: NotificationEntry) =
+ provider.makeAndLogBubbleDecision(entry).let {
+ assertTrue("unexpected suppressed bubble: ${it.logReason}", it.shouldInterrupt)
+ }
+
+ private fun assertShouldNotBubble(entry: NotificationEntry) =
+ provider.makeAndLogBubbleDecision(entry).let {
+ assertFalse("unexpected unsuppressed bubble: ${it.logReason}", it.shouldInterrupt)
+ }
+
+ private fun assertShouldFsi(entry: NotificationEntry) =
+ provider.makeUnloggedFullScreenIntentDecision(entry).let {
+ assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
+ }
+
+ private fun assertShouldNotFsi(entry: NotificationEntry) =
+ provider.makeUnloggedFullScreenIntentDecision(entry).let {
+ assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
+ }
+
+ private class EntryBuilder(val context: Context) {
+ var importance = IMPORTANCE_DEFAULT
+ var suppressedVisualEffects: Int? = null
+ var whenMs: Long? = null
+ var visibilityOverride: Int? = null
+ var hasFsi = false
+ var canBubble: Boolean? = null
+ var hasBubbleMetadata = false
+ var bubbleSuppressNotification: Boolean? = null
+
+ private fun buildBubbleMetadata() =
+ BubbleMetadata.Builder(
+ PendingIntent.getActivity(
+ context,
+ /* requestCode = */ 0,
+ Intent().setPackage(context.packageName),
+ FLAG_MUTABLE
+ ),
+ Icon.createWithResource(context.resources, R.drawable.android)
+ )
+ .apply { bubbleSuppressNotification?.let { setSuppressNotification(it) } }
+ .build()
+
+ fun build() =
+ Notification.Builder(context, TEST_CHANNEL_ID)
+ .apply {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+
+ if (hasFsi) {
+ setFullScreenIntent(mock(), /* highPriority = */ true)
+ }
+
+ whenMs?.let { setWhen(it) }
+
+ if (hasBubbleMetadata) {
+ setBubbleMetadata(buildBubbleMetadata())
+ }
+ }
+ .build()
+ .let { NotificationEntryBuilder().setNotification(it) }
+ .apply {
+ setPkg(TEST_PACKAGE)
+ setOpPkg(TEST_PACKAGE)
+ setTag(TEST_TAG)
+
+ setImportance(importance)
+ setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
+
+ canBubble?.let { setCanBubble(it) }
+ }
+ .build()!!
+ .also {
+ modifyRanking(it)
+ .apply {
+ suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
+ visibilityOverride?.let { setVisibilityOverride(it) }
+ }
+ .build()
+ }
+ }
+
+ private fun buildEntry(block: EntryBuilder.() -> Unit) =
+ EntryBuilder(context).also(block).build()
+
+ private fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ importance = IMPORTANCE_HIGH
+ run(block)
+ }
+
+ private fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ importance = IMPORTANCE_DEFAULT
+ visibilityOverride = VISIBILITY_NO_OVERRIDE
+ run(block)
+ }
+
+ private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ importance = IMPORTANCE_HIGH
+ hasFsi = true
+ run(block)
+ }
+
+ private fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ canBubble = true
+ hasBubbleMetadata = true
+ run(block)
+ }
+
+ private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
}
private const val TEST_CONTENT_TITLE = "Test Content Title"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 60421c98..3a9d111 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -314,7 +314,26 @@
sharedNotificationContainerInteractor.setTopPosition(10f)
assertThat(position)
- .isEqualTo(SharedNotificationContainerPosition(top = 10f, bottom = 0f))
+ .isEqualTo(
+ SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = true)
+ )
+ }
+
+ @Test
+ fun positionOnQS() =
+ testScope.runTest {
+ val position by collectLastValue(underTest.position)
+
+ // Start on lockscreen with shade expanded
+ showLockscreenWithQSExpanded()
+
+ // When not in split shade
+ sharedNotificationContainerInteractor.setTopPosition(10f)
+
+ assertThat(position)
+ .isEqualTo(
+ SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = false)
+ )
}
@Test
@@ -390,6 +409,17 @@
)
}
+ private suspend fun TestScope.showLockscreenWithQSExpanded() {
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeRepository.setQsExpansion(1f)
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this,
+ )
+ }
+
@SysUISingleton
@Component(
modules =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 05fd6d2..4a20f83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -20,6 +20,8 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_ON;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -51,7 +53,6 @@
import android.app.WallpaperManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.IntentFilter;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -180,11 +181,16 @@
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.MessageRouterImpl;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
import com.android.systemui.volume.VolumeComponent;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -198,8 +204,6 @@
import javax.inject.Provider;
-import dagger.Lazy;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -319,6 +323,7 @@
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -349,8 +354,10 @@
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
Handler.createAsync(Looper.myLooper()));
+ mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
+
mNotificationInterruptStateProvider =
- new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
+ new TestableNotificationInterruptStateProviderImpl(
mPowerManager,
mAmbientDisplayConfiguration,
mStatusBarStateController,
@@ -363,7 +370,9 @@
mock(KeyguardNotificationVisibilityProvider.class),
mock(UiEventLogger.class),
mUserTracker,
- mDeviceProvisionedController);
+ mDeviceProvisionedController,
+ mFakeSystemClock,
+ mFakeGlobalSettings);
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -1162,7 +1171,6 @@
NotificationInterruptStateProviderImpl {
TestableNotificationInterruptStateProviderImpl(
- ContentResolver contentResolver,
PowerManager powerManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
StatusBarStateController controller,
@@ -1175,9 +1183,10 @@
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
UiEventLogger uiEventLogger,
UserTracker userTracker,
- DeviceProvisionedController deviceProvisionedController) {
+ DeviceProvisionedController deviceProvisionedController,
+ SystemClock systemClock,
+ GlobalSettings globalSettings) {
super(
- contentResolver,
powerManager,
ambientDisplayConfiguration,
batteryController,
@@ -1190,7 +1199,9 @@
keyguardNotificationVisibilityProvider,
uiEventLogger,
userTracker,
- deviceProvisionedController
+ deviceProvisionedController,
+ systemClock,
+ globalSettings
);
mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ec808c7..8309b85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -18,6 +18,8 @@
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
+import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
+import static android.provider.Settings.Global.HEADS_UP_ON;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
@@ -157,6 +159,8 @@
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -476,7 +480,6 @@
mKeyguardStateController,
mScreenOffAnimationController,
mAuthController,
- mShadeExpansionStateManager,
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor
@@ -507,8 +510,11 @@
when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
Collections.singletonList(mock(UserInfo.class)));
+ final FakeGlobalSettings fakeGlobalSettings = new FakeGlobalSettings();
+ fakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
+
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
- new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
+ new TestableNotificationInterruptStateProviderImpl(
mock(PowerManager.class),
mock(AmbientDisplayConfiguration.class),
mock(StatusBarStateController.class),
@@ -521,7 +527,9 @@
mock(KeyguardNotificationVisibilityProvider.class),
mock(UiEventLogger.class),
mock(UserTracker.class),
- mock(DeviceProvisionedController.class)
+ mock(DeviceProvisionedController.class),
+ mock(SystemClock.class),
+ fakeGlobalSettings
);
mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 0df235d..975555c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.wmshell;
-import android.content.ContentResolver;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
@@ -32,12 +31,13 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
public class TestableNotificationInterruptStateProviderImpl
extends NotificationInterruptStateProviderImpl {
TestableNotificationInterruptStateProviderImpl(
- ContentResolver contentResolver,
PowerManager powerManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
StatusBarStateController statusBarStateController,
@@ -50,8 +50,10 @@
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
UiEventLogger uiEventLogger,
UserTracker userTracker,
- DeviceProvisionedController deviceProvisionedController) {
- super(contentResolver,
+ DeviceProvisionedController deviceProvisionedController,
+ SystemClock systemClock,
+ GlobalSettings globalSettings) {
+ super(
powerManager,
ambientDisplayConfiguration,
batteryController,
@@ -64,7 +66,9 @@
keyguardNotificationVisibilityProvider,
uiEventLogger,
userTracker,
- deviceProvisionedController);
+ deviceProvisionedController,
+ systemClock,
+ globalSettings);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 3c49c58..800593f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -56,6 +56,14 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyExpandedOrAwaitingInputTransfer = _legacyExpandedOrAwaitingInputTransfer
+ private val _legacyIsQsExpanded = MutableStateFlow(false)
+ @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+
+ @Deprecated("Use ShadeInteractor instead")
+ override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
+ _legacyIsQsExpanded.value = legacyIsQsExpanded
+ }
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyExpandedOrAwaitingInputTransfer(
legacyExpandedOrAwaitingInputTransfer: Boolean
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
index 19fdb6d..a65813a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt
@@ -104,7 +104,7 @@
override fun isDreaming() = dreaming
- override fun setIsDreaming(drreaming: Boolean): Boolean {
+ override fun setIsDreaming(dreaming: Boolean): Boolean {
dreaming != this.dreaming || return false
this.dreaming = dreaming
callbacks.forEach { it.onDreamingChanged(dreaming) }
diff --git a/packages/SystemUI/tools/lint/baseline.xml b/packages/SystemUI/tools/lint/baseline.xml
index 301c9b8..43f8300 100644
--- a/packages/SystemUI/tools/lint/baseline.xml
+++ b/packages/SystemUI/tools/lint/baseline.xml
@@ -1271,17 +1271,6 @@
</issue>
<issue
- id="MergeRootFrame"
- message="This `<FrameLayout>` can be replaced with a `<merge>` tag"
- errorLine1="<FrameLayout"
- errorLine2="^">
- <location
- file="res/layout/volume_dnd_icon.xml"
- line="16"
- column="1"/>
- </issue>
-
- <issue
id="InefficientWeight"
message="Use a `layout_height` of `0dp` instead of `wrap_content` for better performance"
errorLine1=" android:layout_height="wrap_content""
@@ -88853,17 +88842,6 @@
errorLine1=" <ImageView"
errorLine2=" ^">
<location
- file="res/layout/volume_dnd_icon.xml"
- line="23"
- column="5"/>
- </issue>
-
- <issue
- id="ContentDescription"
- message="[Accessibility] Missing `contentDescription` attribute on image"
- errorLine1=" <ImageView"
- errorLine2=" ^">
- <location
file="res/layout/wireless_charging_layout.xml"
line="26"
column="5"/>
@@ -89783,15 +89761,4 @@
column="22"/>
</issue>
- <issue
- id="RtlHardcoded"
- message="Use "`end`" instead of "`right`" to ensure correct behavior in right-to-left locales"
- errorLine1=" android:layout_gravity="right|top""
- errorLine2=" ~~~~~~~~~">
- <location
- file="res/layout/volume_dnd_icon.xml"
- line="26"
- column="33"/>
- </issue>
-
</issues>
diff --git a/services/Android.bp b/services/Android.bp
index aca8409..f1534b4 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -41,7 +41,10 @@
name: "system_optimized_java_defaults",
module_type: "java_defaults",
config_namespace: "ANDROID",
- bool_variables: ["SYSTEM_OPTIMIZE_JAVA"],
+ bool_variables: [
+ "SYSTEM_OPTIMIZE_JAVA",
+ "FULL_SYSTEM_OPTIMIZE_JAVA",
+ ],
properties: [
"optimize",
"dxflags",
@@ -56,6 +59,7 @@
enabled: true,
// TODO(b/210510433): Enable optimizations after improving
// retracing infra.
+ // See also FULL_SYSTEM_OPTIMIZE_JAVA.
optimize: false,
shrink: true,
ignore_warnings: false,
@@ -81,6 +85,12 @@
dxflags: ["--debug"],
},
},
+ // Allow form factors to opt-in full system java optimization
+ FULL_SYSTEM_OPTIMIZE_JAVA: {
+ optimize: {
+ optimize: true,
+ },
+ },
},
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 32666e7..bb50a99 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3406,7 +3406,8 @@
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
- || userState.isMagnificationTwoFingerTripleTapEnabledLocked()
+ || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && userState.isMagnificationTwoFingerTripleTapEnabledLocked())
|| userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
@@ -3435,7 +3436,9 @@
return;
}
final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
- || userState.isMagnificationSingleFingerTripleTapEnabledLocked())
+ || userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+ || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && userState.isMagnificationTwoFingerTripleTapEnabledLocked()))
&& (userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
|| userHasMagnificationServicesLocked(userState);
@@ -5133,6 +5136,8 @@
// Remove magnification button UI when the magnification capability is not all mode or
// magnification is disabled.
if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+ || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && userState.isMagnificationTwoFingerTripleTapEnabledLocked())
|| userState.isShortcutMagnificationEnabledLocked())
|| userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 08503cb..3e46ee2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -50,6 +50,8 @@
/**
* Called by the power manager to tell the input method manager whether it
* should start watching for wake events.
+ *
+ * @param interactive the interactive mode parameter
*/
public abstract void setInteractive(boolean interactive);
@@ -61,16 +63,16 @@
/**
* Returns the list of installed input methods for the specified user.
*
- * @param userId The user ID to be queried.
- * @return A list of {@link InputMethodInfo}. VR-only IMEs are already excluded.
+ * @param userId the user ID to be queried
+ * @return a list of {@link InputMethodInfo}. VR-only IMEs are already excluded
*/
public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
/**
* Returns the list of installed input methods that are enabled for the specified user.
*
- * @param userId The user ID to be queried.
- * @return A list of {@link InputMethodInfo} that are enabled for {@code userId}.
+ * @param userId the user ID to be queried
+ * @return a list of {@link InputMethodInfo} that are enabled for {@code userId}
*/
public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
@@ -78,8 +80,10 @@
* Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
* the input method.
*
+ * @param userId the user ID to be queried
* @param requestInfo information needed to create an {@link InlineSuggestionsRequest}.
- * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
+ * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request
+ * object
*/
public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb);
@@ -88,8 +92,8 @@
* Force switch to the enabled input method by {@code imeId} for current user. If the input
* method with {@code imeId} is not enabled or not installed, do nothing.
*
- * @param imeId The input method ID to be switched to.
- * @param userId The user ID to be queried.
+ * @param imeId the input method ID to be switched to
+ * @param userId the user ID to be queried
* @return {@code true} if the current input method was successfully switched to the input
* method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
* to be switched.
@@ -100,28 +104,30 @@
* Force enable or disable the input method associated with {@code imeId} for given user. If
* the input method associated with {@code imeId} is not installed, do nothing.
*
- * @param imeId The input method ID to be enabled or disabled.
+ * @param imeId the input method ID to be enabled or disabled
* @param enabled {@code true} if the input method associated with {@code imeId} should be
- * enabled.
- * @param userId The user ID to be queried.
+ * enabled
+ * @param userId the user ID to be queried
* @return {@code true} if the input method associated with {@code imeId} was successfully
- * enabled or disabled, {@code false} if the input method specified is not installed
- * or was unable to be enabled/disabled for some other reason.
+ * enabled or disabled, {@code false} if the input method specified is not installed
+ * or was unable to be enabled/disabled for some other reason.
*/
public abstract boolean setInputMethodEnabled(String imeId, boolean enabled,
@UserIdInt int userId);
/**
* Registers a new {@link InputMethodListListener}.
+ *
+ * @param listener the listener to add
*/
public abstract void registerInputMethodListListener(InputMethodListListener listener);
/**
* Transfers input focus from a given input token to that of the IME window.
*
- * @param sourceInputToken The source token.
- * @param displayId The display hosting the IME window.
- * @return {@code true} if the transfer is successful.
+ * @param sourceInputToken the source token.
+ * @param displayId the display hosting the IME window
+ * @return {@code true} if the transfer is successful
*/
public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
int displayId);
@@ -132,7 +138,7 @@
* or SystemUI).
*
* @param windowToken the window token that is now in control, or {@code null} if no client
- * window is in control of the IME.
+ * window is in control of the IME
*/
public abstract void reportImeControl(@Nullable IBinder windowToken);
@@ -163,8 +169,8 @@
* Callback when the IInputMethodSession from the accessibility service with the specified
* accessibilityConnectionId is created.
*
- * @param accessibilityConnectionId The connection id of the accessibility service.
- * @param session The session passed back from the accessibility service.
+ * @param accessibilityConnectionId the connection id of the accessibility service
+ * @param session the session passed back from the accessibility service
*/
public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session);
@@ -173,7 +179,7 @@
* Unbind the accessibility service with the specified accessibilityConnectionId from current
* client.
*
- * @param accessibilityConnectionId The connection id of the accessibility service.
+ * @param accessibilityConnectionId the connection id of the accessibility service
*/
public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
@@ -181,7 +187,7 @@
* Switch the keyboard layout in response to a keyboard shortcut.
*
* @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
- * previous subtype.
+ * previous subtype
*/
public abstract void switchKeyboardLayout(int direction);
@@ -192,7 +198,7 @@
public abstract boolean isAnyInputConnectionActive();
/**
- * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
+ * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
private static final InputMethodManagerInternal NOP =
new InputMethodManagerInternal() {
@@ -282,7 +288,7 @@
};
/**
- * @return Global instance if exists. Otherwise, a fallback no-op instance.
+ * @return Global instance if exists. Otherwise, a fallback no-op instance.
*/
@NonNull
public static InputMethodManagerInternal get() {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2951ef6..40d3ef0 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1867,8 +1867,7 @@
final File targetDir = resolveTargetDir(request.getInstallFlags(), request.getCodeFile());
final File beforeCodeFile = request.getCodeFile();
- final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir,
- parsedPackage.getPackageName());
+ final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir);
if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
final boolean onIncremental = mPm.mIncrementalManager != null
@@ -3099,8 +3098,7 @@
return null;
}
final File dstCodePath =
- PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null),
- packageName);
+ PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null));
int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName);
if (ret == PackageManager.INSTALL_SUCCEEDED) {
ret = PackageManagerServiceUtils.extractNativeBinaries(dstCodePath, packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index bcb7bde..f8e909e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -30,7 +30,6 @@
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
-import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
@@ -1299,16 +1298,15 @@
}
/**
- * Given {@code targetDir}, returns {@code targetDir/~~[randomStrA]/[packageName]-[randomStrB].}
+ * Given {@code targetDir}, returns {@code targetDir/~~[randomStrA]/[randomStrB].}
* Makes sure that {@code targetDir/~~[randomStrA]} directory doesn't exist.
* Notice that this method doesn't actually create any directory.
*
* @param targetDir Directory that is two-levels up from the result directory.
- * @param packageName Name of the package whose code files are to be installed under the result
- * directory.
- * @return File object for the directory that should hold the code files of {@code packageName}.
+ *
+ * @return File object for the directory that should hold the code files.
*/
- public static File getNextCodePath(File targetDir, String packageName) {
+ public static File getNextCodePath(File targetDir) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[16];
File firstLevelDir;
@@ -1320,22 +1318,8 @@
} while (firstLevelDir.exists());
random.nextBytes(bytes);
- String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes,
- Base64.URL_SAFE | Base64.NO_WRAP);
- final File result = new File(firstLevelDir, dirName);
- if (DEBUG && !Objects.equals(tryParsePackageName(result.getName()), packageName)) {
- throw new RuntimeException(
- "codepath is off: " + result.getName() + " (" + packageName + ")");
- }
- return result;
- }
-
- static String tryParsePackageName(@NonNull String codePath) throws IllegalArgumentException {
- int packageNameEnds = codePath.indexOf(RANDOM_CODEPATH_PREFIX);
- if (packageNameEnds == -1) {
- throw new IllegalArgumentException("Not a valid package folder name");
- }
- return codePath.substring(0, packageNameEnds);
+ String dirName = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
+ return new File(firstLevelDir, dirName);
}
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 534f176..c97fbda 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3836,7 +3836,8 @@
if (type == XmlPullParser.START_TAG) {
final String name = parser.getName();
if (name.equals(TAG_USER)) {
- UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+ UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID),
+ mUserVersion);
if (userData != null) {
synchronized (mUsersLock) {
@@ -4555,7 +4556,7 @@
}
@GuardedBy({"mPackagesLock"})
- private UserData readUserLP(int id) {
+ private UserData readUserLP(int id, int userVersion) {
try (ResilientAtomicFile file = getUserFile(id)) {
FileInputStream fis = null;
try {
@@ -4564,19 +4565,19 @@
Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
return null;
}
- return readUserLP(id, fis);
+ return readUserLP(id, fis, userVersion);
} catch (Exception e) {
// Remove corrupted file and retry.
Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
file.failRead(fis, e);
- return readUserLP(id);
+ return readUserLP(id, userVersion);
}
}
}
@GuardedBy({"mPackagesLock"})
@VisibleForTesting
- UserData readUserLP(int id, InputStream is) throws IOException,
+ UserData readUserLP(int id, InputStream is, int userVersion) throws IOException,
XmlPullParserException {
int flags = 0;
String userType = null;
@@ -4669,7 +4670,17 @@
} else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) {
- localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+ if (userVersion < 10) {
+ // Prior to version 10, the local user restrictions were stored as sub tags
+ // grouped by the user id of the source user. The source is no longer stored
+ // on versions 10+ as this is now stored in the DevicePolicyEngine.
+ RestrictionsSet oldLocalRestrictions =
+ RestrictionsSet.readRestrictions(
+ parser, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
+ localRestrictions = oldLocalRestrictions.mergeAll();
+ } else {
+ localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+ }
} else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_GUEST_RESTRICTIONS.equals(tag)) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 077812b..45ca690 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
+import static android.app.AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.AppOpsManager.OP_TOAST_WINDOW;
import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
@@ -31,6 +32,7 @@
import static android.os.Build.VERSION_CODES.O;
import static android.os.IInputConstants.INVALID_INPUT_DEVICE_ID;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
+import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.STATE_OFF;
@@ -2992,12 +2994,16 @@
// Window manager does the checking for this.
outAppOp[0] = OP_TOAST_WINDOW;
return ADD_OKAY;
+ case TYPE_ACCESSIBILITY_OVERLAY:
+ if (createAccessibilityOverlayAppOpEnabled()) {
+ outAppOp[0] = OP_CREATE_ACCESSIBILITY_OVERLAY;
+ return ADD_OKAY;
+ }
case TYPE_INPUT_METHOD:
case TYPE_WALLPAPER:
case TYPE_PRESENTATION:
case TYPE_PRIVATE_PRESENTATION:
case TYPE_VOICE_INTERACTION:
- case TYPE_ACCESSIBILITY_OVERLAY:
case TYPE_QS_DIALOG:
case TYPE_NAVIGATION_BAR_PANEL:
// The window manager will check these.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3c8e630..26f0d34 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1585,7 +1585,7 @@
* the Activities in the Task should be finished when it finishes. Otherwise, return {@code
* false}.
*/
- private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
+ private static boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
// Not a relative root if the given Activity is not the root Activity of its TaskFragment.
final TaskFragment taskFragment = r.getTaskFragment();
if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r,
@@ -1598,7 +1598,7 @@
return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment;
}
- private boolean isTopActivityInTaskFragment(ActivityRecord activity) {
+ private static boolean isTopActivityInTaskFragment(ActivityRecord activity) {
return activity.getTaskFragment().topRunningActivity() == activity;
}
@@ -1614,9 +1614,6 @@
public void onBackPressed(IBinder token, IRequestFinishCallback callback) {
final long origId = Binder.clearCallingIdentity();
try {
- final Intent baseActivityIntent;
- final boolean launchedFromHome;
- final boolean isLastRunningActivity;
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
@@ -1624,39 +1621,16 @@
final Task task = r.getTask();
final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/,
true /*setToBottomIfNone*/);
- final boolean isTaskRoot = r == root;
- if (isTaskRoot) {
- if (mService.mWindowOrganizerController.mTaskOrganizerController
+ if (r == root && mService.mWindowOrganizerController.mTaskOrganizerController
.handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
- // This task is handled by a task organizer that has requested the back
- // pressed callback.
- return;
- }
- } else if (!isRelativeTaskRootActivity(r, root)) {
- // Finish the Activity if the activity is not the task root or relative root.
- requestCallbackFinish(callback);
+ // This task is handled by a task organizer that has requested the back
+ // pressed callback.
return;
}
-
- isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r);
-
- final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity);
- baseActivityIntent = isBaseActivity ? root.intent : null;
-
- launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
- }
-
- // If the activity was launched directly from the home screen, then we should
- // refrain from finishing the activity and instead move it to the back to keep it in
- // memory. The requirements for this are:
- // 1. The activity is the last running activity in the task.
- // 2. The current activity is the base activity for the task.
- // 3. The activity was launched by the home process, and is one of the main entry
- // points for the application.
- if (baseActivityIntent != null && isLastRunningActivity
- && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) {
- moveActivityTaskToBack(token, true /* nonRoot */);
- return;
+ if (shouldMoveTaskToBack(r, root)) {
+ moveActivityTaskToBack(token, true /* nonRoot */);
+ return;
+ }
}
// The default option for handling the back button is to finish the Activity.
@@ -1666,6 +1640,27 @@
}
}
+ static boolean shouldMoveTaskToBack(ActivityRecord r, ActivityRecord rootActivity) {
+ if (r != rootActivity && !isRelativeTaskRootActivity(r, rootActivity)) {
+ return false;
+ }
+ final boolean isBaseActivity = rootActivity.mActivityComponent.equals(
+ r.getTask().realActivity);
+ final Intent baseActivityIntent = isBaseActivity ? rootActivity.intent : null;
+
+ // If the activity was launched directly from the home screen, then we should
+ // refrain from finishing the activity and instead move it to the back to keep it in
+ // memory. The requirements for this are:
+ // 1. The activity is the last running activity in the task.
+ // 2. The current activity is the base activity for the task.
+ // 3. The activity was launched by the home process, and is one of the main entry
+ // points for the application.
+ return baseActivityIntent != null
+ && isTopActivityInTaskFragment(r)
+ && rootActivity.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME)
+ && ActivityRecord.isMainIntent(baseActivityIntent);
+ }
+
@Override
public void enableTaskLocaleOverride(IBinder token) {
if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d1728d6..2d37b9b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -43,6 +43,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -64,6 +65,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -141,10 +143,6 @@
// multiple Activities in the Stack.
Task prevTask = null;
- // The previous activity we're going back to. This can be either a child of currentTask
- // if there are more than one Activity in currentTask, or a child of prevTask, if
- // currentActivity is the last child of currentTask.
- ActivityRecord prevActivity;
WindowContainer<?> removedWindowContainer = null;
WindowState window;
@@ -264,14 +262,21 @@
return infoBuilder.build();
}
+ // The previous activity we're going back to. This can be either a child of currentTask
+ // if there are more than one Activity in currentTask, or a child of prevTask, if
+ // currentActivity is the last child of currentTask.
// We don't have an application callback, let's find the destination of the back gesture
// The search logic should align with ActivityClientController#finishActivity
- prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
+ final ArrayList<ActivityRecord> prevActivities = new ArrayList<>();
+ final boolean canAnimate = getAnimatablePrevActivities(currentTask, currentActivity,
+ prevActivities);
final boolean isOccluded = isKeyguardOccluded(window);
- // TODO Dialog window does not need to attach on activity, check
- // window.mAttrs.type != TYPE_BASE_APPLICATION
- if ((window.getParent().getChildCount() > 1
+ if (!canAnimate) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else if ((window.getParent().getChildCount() > 1
&& window.getParent().getChildAt(0) != window)) {
+ // TODO Dialog window does not need to attach on activity, check
+ // window.mAttrs.type != TYPE_BASE_APPLICATION
// Are we the top window of our parent? If not, we are a window on top of the
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
@@ -279,9 +284,8 @@
} else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
// skip if current activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
- removedWindowContainer = window;
- } else if (prevActivity != null) {
- if (!isOccluded || prevActivity.canShowWhenLocked()) {
+ } else if (prevActivities.size() > 0) {
+ if (!isOccluded || prevActivities.get(0).canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
final WindowContainer parent = currentActivity.getParent();
final boolean canCustomize = parent != null
@@ -303,40 +307,45 @@
}
}
removedWindowContainer = currentActivity;
- prevTask = prevActivity.getTask();
+ prevTask = prevActivities.get(0).getTask();
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
} else {
+ // keyguard locked and activities are unable to show when locked.
backType = BackNavigationInfo.TYPE_CALLBACK;
}
- } else if (currentTask.returnsToHomeRootTask()) {
- if (isOccluded) {
- backType = BackNavigationInfo.TYPE_CALLBACK;
- } else {
- // Our Task should bring back to home
- removedWindowContainer = currentTask;
- prevTask = currentTask.getDisplayArea().getRootHomeTask();
- backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
- mShowWallpaper = true;
- }
- } else if (currentActivity.isRootOfTask()) {
+ } else {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
- prevTask = currentTask.mRootWindowContainer.getTask(Task::showToCurrentUser,
- currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
- removedWindowContainer = currentTask;
- // If it reaches the top activity, we will check the below task from parent.
- // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
- // or set the type to proper value when it's return to home or another task.
- if (prevTask == null || prevTask.inMultiWindowMode()) {
+ prevTask = currentTask.mRootWindowContainer.getTask(t -> {
+ if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
+ final ActivityRecord ar = t.getTopNonFinishingActivity();
+ return ar != null && ar.showToCurrentUser();
+ }
+ return false;
+ }, currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+ final ActivityRecord tmpPre = prevTask.getTopNonFinishingActivity();
+ if (tmpPre != null) {
+ prevActivities.add(tmpPre);
+ findAdjacentActivityIfExist(tmpPre, prevActivities);
+ }
+ if (prevActivities.isEmpty()
+ || (isOccluded && !prevActivities.get(0).canShowWhenLocked())) {
backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else if (prevTask.isActivityTypeHome()) {
+ removedWindowContainer = currentTask;
+ backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ mShowWallpaper = true;
} else {
- prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevActivity == null || (isOccluded && !prevActivity.canShowWhenLocked())) {
+ // If it reaches the top activity, we will check the below task from parent.
+ // If it's null or multi-window and has different parent task, fallback the type
+ // to TYPE_CALLBACK. Or set the type to proper value when it's return to home or
+ // another task.
+ final Task prevParent = prevTask.getParent().asTask();
+ final Task currParent = currentTask.getParent().asTask();
+ if (prevTask.inMultiWindowMode() && prevParent != currParent) {
backType = BackNavigationInfo.TYPE_CALLBACK;
- } else if (prevTask.isActivityTypeHome()) {
- backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
- mShowWallpaper = true;
} else {
+ removedWindowContainer = prevTask;
backType = BackNavigationInfo.TYPE_CROSS_TASK;
}
}
@@ -345,7 +354,8 @@
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s "
+ "removedContainer:%s, backType=%s",
- prevActivity != null ? prevActivity.mActivityComponent : null,
+ prevActivities.size() > 0 ? TextUtils.join(";", prevActivities.stream()
+ .map(r -> r.mActivityComponent).toArray()) : null,
prevTask != null ? prevTask.getName() : null,
removedWindowContainer,
BackNavigationInfo.typeToString(backType));
@@ -360,7 +370,7 @@
if (prepareAnimation) {
final AnimationHandler.ScheduleAnimationBuilder builder =
mAnimationHandler.prepareAnimation(backType, adapter,
- currentTask, prevTask, currentActivity, prevActivity);
+ currentTask, prevTask, currentActivity, prevActivities);
mBackAnimationInProgress = builder != null;
if (mBackAnimationInProgress) {
if (removedWindowContainer.hasCommittedReparentToAnimationLeash()
@@ -372,8 +382,8 @@
// Current transition is still running, we have to defer the hiding to the
// client process to prevent the unexpected relayout when handling the back
// animation.
- if (prevActivity != null) {
- prevActivity.setDeferHidingClient(true);
+ for (int i = prevActivities.size() - 1; i >= 0; --i) {
+ prevActivities.get(i).setDeferHidingClient(true);
}
} else {
scheduleAnimation(builder);
@@ -381,17 +391,91 @@
}
}
infoBuilder.setPrepareRemoteAnimation(prepareAnimation);
- } // Release wm Lock
- WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
- if (finalRemovedWindowContainer != null) {
- final int finalBackType = backType;
- RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
- result, finalBackType));
- infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
+ if (removedWindowContainer != null) {
+ final int finalBackType = backType;
+ final RemoteCallback onBackNavigationDone = new RemoteCallback(result ->
+ onBackNavigationDone(result, finalBackType));
+ infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
+ } else {
+ mNavigationMonitor.stopMonitorForRemote();
+ }
+ mLastBackType = backType;
+ return infoBuilder.build();
}
- mLastBackType = backType;
- return infoBuilder.build();
+ }
+
+ /**
+ * Gets previous activities from currentActivity.
+ *
+ * @return false if unable to predict what will happen
+ */
+ private static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
+ @NonNull ActivityRecord currentActivity,
+ @NonNull ArrayList<ActivityRecord> outPrevActivities) {
+ if (currentActivity.mAtmService
+ .mTaskOrganizerController.shouldInterceptBackPressedOnRootTask(
+ currentTask.getRootTask())) {
+ // The task organizer will handle back pressed, don't play animation.
+ return false;
+ }
+ final ActivityRecord root = currentTask.getRootActivity(false /*ignoreRelinquishIdentity*/,
+ true /*setToBottomIfNone*/);
+ if (root != null && ActivityClientController.shouldMoveTaskToBack(currentActivity, root)) {
+ return true;
+ }
+
+ // Searching previous
+ final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing,
+ currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+ if (prevActivity == null) {
+ // No previous activity in this task, can still predict if previous task exists.
+ return true;
+ }
+ if (currentTask.getActivity((above) -> !above.finishing, currentActivity,
+ false /*includeBoundary*/, false /*traverseTopToBottom*/) != null) {
+ // another activity is above this activity, don't know what will happen
+ return false;
+ }
+
+ final TaskFragment currTF = currentActivity.getTaskFragment();
+ final TaskFragment prevTF = prevActivity.getTaskFragment();
+ if (currTF != prevTF && prevTF != null) {
+ final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
+ if (prevTFAdjacent != null) {
+ if (prevTFAdjacent == currTF) {
+ // Cannot predict what will happen when app receive back key, skip animation.
+ outPrevActivities.clear();
+ return false;
+ } else {
+ final ActivityRecord prevActivityAdjacent =
+ prevTFAdjacent.getTopNonFinishingActivity();
+ if (prevActivityAdjacent != null) {
+ outPrevActivities.add(prevActivityAdjacent);
+ } else {
+ // Don't know what will happen.
+ outPrevActivities.clear();
+ return false;
+ }
+ }
+ }
+ }
+ outPrevActivities.add(prevActivity);
+ return true;
+ }
+
+ private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity,
+ @NonNull ArrayList<ActivityRecord> outList) {
+ final TaskFragment mainTF = mainActivity.getTaskFragment();
+ if (mainTF == null || mainTF.getAdjacentTaskFragment() == null) {
+ return;
+ }
+ final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
+ final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
+ if (topActivity == null) {
+ return;
+ }
+ outList.add(topActivity);
}
boolean isMonitoringTransition() {
@@ -696,7 +780,7 @@
if (!hasTarget) {
// Skip if no target participated in current finished transition.
Slog.w(TAG, "Finished transition didn't include the targets"
- + " open: " + mPendingAnimationBuilder.mOpenTarget
+ + " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
+ " close: " + mPendingAnimationBuilder.mCloseTarget);
cancelPendingAnimation();
return false;
@@ -732,7 +816,7 @@
private final boolean mShowWindowlessSurface;
private final WindowManagerService mWindowManagerService;
private BackWindowAnimationAdaptor mCloseAdaptor;
- private BackWindowAnimationAdaptor mOpenAdaptor;
+ private BackWindowAnimationAdaptorWrapper mOpenAnimAdaptor;
private boolean mComposed;
private boolean mWaitTransition;
private int mSwitchType = UNKNOWN;
@@ -741,7 +825,7 @@
// exactly match animating target. When target match, reparent the starting surface to
// the opening target like starting window do.
private boolean mStartingSurfaceTargetMatch;
- private ActivityRecord mOpenActivity;
+ private ActivityRecord[] mOpenActivities;
AnimationHandler(WindowManagerService wms) {
mWindowManagerService = wms;
@@ -753,61 +837,69 @@
private static final int TASK_SWITCH = 1;
private static final int ACTIVITY_SWITCH = 2;
- private static boolean isActivitySwitch(WindowContainer close, WindowContainer open) {
- if (close.asActivityRecord() == null || open.asActivityRecord() == null
- || (close.asActivityRecord().getTask()
- != open.asActivityRecord().getTask())) {
+ private static boolean isActivitySwitch(@NonNull WindowContainer close,
+ @NonNull WindowContainer[] open) {
+ if (open == null || open.length == 0 || close.asActivityRecord() == null) {
return false;
}
+ final Task closeTask = close.asActivityRecord().getTask();
+ for (int i = open.length - 1; i >= 0; --i) {
+ if (open[i].asActivityRecord() == null
+ || (closeTask != open[i].asActivityRecord().getTask())) {
+ return false;
+ }
+ }
return true;
}
- private static boolean isTaskSwitch(WindowContainer close, WindowContainer open) {
- if (close.asTask() == null || open.asTask() == null
- || (close.asTask() == open.asTask())) {
+ private static boolean isTaskSwitch(@NonNull WindowContainer close,
+ @NonNull WindowContainer[] open) {
+ if (open == null || open.length != 1 || close.asTask() == null) {
return false;
}
- return true;
+ return open[0].asTask() != null && (close.asTask() != open[0].asTask());
}
- private void initiate(WindowContainer close, WindowContainer open,
- ActivityRecord openActivity) {
- WindowContainer closeTarget;
+ private void initiate(@NonNull WindowContainer close, @NonNull WindowContainer[] open,
+ @NonNull ActivityRecord[] openingActivities) {
if (isActivitySwitch(close, open)) {
mSwitchType = ACTIVITY_SWITCH;
- closeTarget = close.asActivityRecord();
} else if (isTaskSwitch(close, open)) {
mSwitchType = TASK_SWITCH;
- // The transition target must be activity in legacy transition.
- closeTarget = WindowManagerService.sEnableShellTransitions ? close
- : close.asTask().getTopNonFinishingActivity();
} else {
mSwitchType = UNKNOWN;
return;
}
- mCloseAdaptor = createAdaptor(closeTarget, false, mSwitchType);
- mOpenAdaptor = createAdaptor(open, true, mSwitchType);
- mOpenActivity = openActivity;
- if (mCloseAdaptor.mAnimationTarget == null || mOpenAdaptor.mAnimationTarget == null) {
+ mCloseAdaptor = createAdaptor(close, false, mSwitchType);
+ if (mCloseAdaptor.mAnimationTarget == null) {
Slog.w(TAG, "composeNewAnimations fail, skip");
clearBackAnimateTarget();
+ return;
}
+
+ mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
+ if (!mOpenAnimAdaptor.isValid()) {
+ Slog.w(TAG, "compose animations fail, skip");
+ clearBackAnimateTarget();
+ return;
+ }
+ mOpenActivities = openingActivities;
}
private boolean composeAnimations(@NonNull WindowContainer close,
- @NonNull WindowContainer open, ActivityRecord openActivity) {
+ @NonNull WindowContainer[] open, @NonNull ActivityRecord[] openingActivities) {
if (mComposed || mWaitTransition) {
Slog.e(TAG, "Previous animation is running " + this);
return false;
}
clearBackAnimateTarget();
- if (close == null || open == null || openActivity == null) {
+ if (close == null || open == null || open.length == 0 || open.length > 2) {
Slog.e(TAG, "reset animation with null target close: "
- + close + " open: " + open);
+ + close + " open: " + Arrays.toString(open));
return false;
}
- initiate(close, open, openActivity);
+ initiate(close, open, openingActivities);
if (mSwitchType == UNKNOWN) {
return false;
}
@@ -816,9 +908,14 @@
return true;
}
- RemoteAnimationTarget[] getAnimationTargets() {
- return mComposed ? new RemoteAnimationTarget[] {
- mCloseAdaptor.mAnimationTarget, mOpenAdaptor.mAnimationTarget} : null;
+ @Nullable RemoteAnimationTarget[] getAnimationTargets() {
+ if (!mComposed) {
+ return null;
+ }
+ final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[2];
+ targets[0] = mCloseAdaptor.mAnimationTarget;
+ targets[1] = mOpenAnimAdaptor.getOrCreateAnimationTarget();
+ return targets;
}
boolean isSupportWindowlessSurface() {
@@ -826,7 +923,7 @@
.isSupportWindowlessStartingSurface();
}
- boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
+ boolean containTarget(@NonNull ArrayList<WindowContainer> wcs, boolean open) {
for (int i = wcs.size() - 1; i >= 0; --i) {
if (isTarget(wcs.get(i), open)) {
return true;
@@ -835,22 +932,35 @@
return wcs.isEmpty();
}
- boolean isTarget(WindowContainer wc, boolean open) {
+ boolean isTarget(@NonNull WindowContainer wc, boolean open) {
if (!mComposed) {
return false;
}
+ if (open) {
+ for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+ if (isAnimateTarget(wc, mOpenAnimAdaptor.mAdaptors[i].mTarget, mSwitchType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return isAnimateTarget(wc, mCloseAdaptor.mTarget, mSwitchType);
+ }
- // WC must be ActivityRecord in legacy transition, but it also can be Task or
- // TaskFragment when using Shell transition.
- // Open target: Can be Task or ActivityRecord or TaskFragment
- // Close target: Limit to the top activity for now, to reduce the chance of misjudgment.
- final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget;
- if (mSwitchType == TASK_SWITCH) {
- return wc == target
- || (wc.asTask() != null && wc.hasChild(target))
- || (wc.asActivityRecord() != null && target.hasChild(wc));
- } else if (mSwitchType == ACTIVITY_SWITCH) {
- return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target));
+ private static boolean isAnimateTarget(@NonNull WindowContainer window,
+ @NonNull WindowContainer animationTarget, int switchType) {
+ if (switchType == TASK_SWITCH) {
+ // simplify home search for multiple hierarchy
+ if (window.isActivityTypeHome() && animationTarget.isActivityTypeHome()) {
+ return true;
+ }
+ return window == animationTarget
+ || (animationTarget.asTask() != null && animationTarget.hasChild(window))
+ || (animationTarget.asActivityRecord() != null
+ && window.hasChild(animationTarget));
+ } else if (switchType == ACTIVITY_SWITCH) {
+ return window == animationTarget
+ || (window.asTaskFragment() != null && window.hasChild(animationTarget));
}
return false;
}
@@ -864,19 +974,25 @@
mCloseAdaptor.mTarget.cancelAnimation();
mCloseAdaptor = null;
}
- if (mOpenAdaptor != null) {
- mOpenAdaptor.cleanUpWindowlessSurface(mStartingSurfaceTargetMatch);
- mOpenAdaptor.mTarget.cancelAnimation();
- mOpenAdaptor = null;
+ if (mOpenAnimAdaptor != null) {
+ mOpenAnimAdaptor.cleanUp(mStartingSurfaceTargetMatch);
+ mOpenAnimAdaptor = null;
}
- if (mOpenActivity != null && mOpenActivity.mLaunchTaskBehind) {
- restoreLaunchBehind(mOpenActivity);
+
+ if (mOpenActivities != null) {
+ for (int i = mOpenActivities.length - 1; i >= 0; --i) {
+ if (mOpenActivities[i].mLaunchTaskBehind) {
+ restoreLaunchBehind(mOpenActivities[i]);
+ }
+ }
}
}
void markStartingSurfaceMatch() {
mStartingSurfaceTargetMatch = true;
- mOpenAdaptor.reparentWindowlessSurfaceToTarget();
+ for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+ mOpenAnimAdaptor.mAdaptors[i].reparentWindowlessSurfaceToTarget();
+ }
}
void clearBackAnimateTarget() {
@@ -885,13 +1001,13 @@
mWaitTransition = false;
mStartingSurfaceTargetMatch = false;
mSwitchType = UNKNOWN;
- mOpenActivity = null;
+ mOpenActivities = null;
}
// The close target must in close list
// The open target can either in close or open list
- boolean containsBackAnimationTargets(ArrayList<WindowContainer> openApps,
- ArrayList<WindowContainer> closeApps) {
+ boolean containsBackAnimationTargets(@NonNull ArrayList<WindowContainer> openApps,
+ @NonNull ArrayList<WindowContainer> closeApps) {
return containTarget(closeApps, false /* open */)
&& (containTarget(openApps, true /* open */)
|| containTarget(openApps, false /* open */));
@@ -901,9 +1017,9 @@
public String toString() {
return "AnimationTargets{"
+ " openTarget= "
- + (mOpenAdaptor != null ? mOpenAdaptor.mTarget : "null")
+ + (mOpenAnimAdaptor != null ? dumpOpenAnimTargetsToString() : null)
+ " closeTarget= "
- + (mCloseAdaptor != null ? mCloseAdaptor.mTarget : "null")
+ + (mCloseAdaptor != null ? mCloseAdaptor.mTarget : null)
+ " mSwitchType= "
+ mSwitchType
+ " mComposed= "
@@ -913,8 +1029,21 @@
+ '}';
}
- private static BackWindowAnimationAdaptor createAdaptor(
- WindowContainer target, boolean isOpen, int switchType) {
+ private String dumpOpenAnimTargetsToString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ for (int i = 0; i < mOpenAnimAdaptor.mAdaptors.length; i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mOpenAnimAdaptor.mAdaptors[i].mTarget);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @NonNull private static BackWindowAnimationAdaptor createAdaptor(
+ @NonNull WindowContainer target, boolean isOpen, int switchType) {
final BackWindowAnimationAdaptor adaptor =
new BackWindowAnimationAdaptor(target, isOpen, switchType);
final SurfaceControl.Transaction pt = target.getPendingTransaction();
@@ -930,6 +1059,100 @@
return adaptor;
}
+ private static class BackWindowAnimationAdaptorWrapper {
+ final BackWindowAnimationAdaptor[] mAdaptors;
+ SurfaceControl.Transaction mCloseTransaction;
+
+ BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
+ @NonNull WindowContainer... targets) {
+ mAdaptors = new BackWindowAnimationAdaptor[targets.length];
+ for (int i = targets.length - 1; i >= 0; --i) {
+ mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType);
+ }
+ }
+
+ boolean isValid() {
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ if (mAdaptors[i].mAnimationTarget == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void cleanUp(boolean startingSurfaceMatch) {
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ mAdaptors[i].cleanUpWindowlessSurface(startingSurfaceMatch);
+ mAdaptors[i].mTarget.cancelAnimation();
+ }
+ if (mCloseTransaction != null) {
+ mCloseTransaction.apply();
+ mCloseTransaction = null;
+ }
+ }
+
+ void onAnimationFinish() {
+ final SurfaceControl.Transaction pt = mAdaptors[0].mTarget.getPendingTransaction();
+ if (mCloseTransaction != null) {
+ pt.merge(mCloseTransaction);
+ mCloseTransaction = null;
+ }
+ if (mAdaptors.length > 1) {
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ final WindowContainer wc = mAdaptors[i].mTarget;
+ final WindowContainer parent = wc.getParent();
+ if (parent != null) {
+ pt.reparent(wc.getSurfaceControl(),
+ parent.getSurfaceControl());
+ }
+ }
+ }
+ }
+
+ @NonNull RemoteAnimationTarget getOrCreateAnimationTarget() {
+ // Special handle for opening two activities together.
+ // If we animate both activities separately, the animation area and rounded corner
+ // would also being handled separately. To make them seem like "open" together, wrap
+ // their leash with another animation leash.
+ if (mAdaptors.length > 1 && mCloseTransaction == null) {
+ final Rect unionBounds = new Rect();
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
+ }
+ final WindowContainer wc = mAdaptors[0].mTarget;
+ final Task task = wc.asActivityRecord() != null
+ ? wc.asActivityRecord().getTask() : wc.asTask();
+ final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
+ final SurfaceControl leashSurface = new SurfaceControl.Builder()
+ .setName("cross-animation-leash")
+ .setContainerLayer()
+ .setHidden(false)
+ .setParent(task.getSurfaceControl())
+ .build();
+ final SurfaceControl.Transaction pt = wc.getPendingTransaction();
+ pt.setLayer(leashSurface, wc.getParent().getLastLayer());
+ mCloseTransaction = new SurfaceControl.Transaction();
+ mCloseTransaction.reparent(leashSurface, null);
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ BackWindowAnimationAdaptor adaptor = mAdaptors[i];
+ pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
+ pt.setPosition(adaptor.mAnimationTarget.leash,
+ adaptor.mAnimationTarget.localBounds.left,
+ adaptor.mAnimationTarget.localBounds.top);
+ }
+ return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
+ represent.isTranslucent, represent.clipRect, represent.contentInsets,
+ represent.prefixOrderIndex,
+ new Point(unionBounds.left, unionBounds.top),
+ unionBounds, unionBounds, represent.windowConfiguration,
+ true /* isNotInRecents */, null, null, represent.taskInfo,
+ represent.allowEnterPip);
+ } else {
+ return mAdaptors[0].mAnimationTarget;
+ }
+ }
+ }
+
private static class BackWindowAnimationAdaptor implements AnimationAdapter {
SurfaceControl mCapturedLeash;
private final Rect mBounds = new Rect();
@@ -943,7 +1166,7 @@
private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
private SurfaceControl mStartingSurface;
- BackWindowAnimationAdaptor(WindowContainer target, boolean isOpen,
+ BackWindowAnimationAdaptor(@NonNull WindowContainer target, boolean isOpen,
int switchType) {
mBounds.set(target.getBounds());
mTarget = target;
@@ -1034,7 +1257,7 @@
return mAnimationTarget;
}
- void createStartingSurface() {
+ void createStartingSurface(@NonNull WindowContainer closeWindow) {
if (!mIsOpen) {
return;
}
@@ -1053,7 +1276,10 @@
final TaskSnapshot snapshot = getSnapshot(mTarget);
mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
.addWindowlessStartingSurface(openTask, mainActivity,
- mAnimationTarget.leash, snapshot,
+ // Choose configuration from closeWindow, because the configuration
+ // of opening target may not update before resume, so the starting
+ // surface should occlude it entirely.
+ mAnimationTarget.leash, snapshot, closeWindow.getConfiguration(),
new IWindowlessStartingSurfaceCallback.Stub() {
// Once the starting surface has been created in shell, it will call
// onSurfaceAdded to pass the created surface to core, so if a
@@ -1108,15 +1334,17 @@
ScheduleAnimationBuilder prepareAnimation(int backType, BackAnimationAdapter adapter,
Task currentTask, Task previousTask, ActivityRecord currentActivity,
- ActivityRecord previousActivity) {
+ ArrayList<ActivityRecord> previousActivity) {
switch (backType) {
case BackNavigationInfo.TYPE_RETURN_TO_HOME:
return new ScheduleAnimationBuilder(backType, adapter)
.setIsLaunchBehind(true)
.setComposeTarget(currentTask, previousTask);
case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
+ ActivityRecord[] prevActs = new ActivityRecord[previousActivity.size()];
+ prevActs = previousActivity.toArray(prevActs);
return new ScheduleAnimationBuilder(backType, adapter)
- .setComposeTarget(currentActivity, previousActivity)
+ .setComposeTarget(currentActivity, prevActs)
.setIsLaunchBehind(false);
case BackNavigationInfo.TYPE_CROSS_TASK:
return new ScheduleAnimationBuilder(backType, adapter)
@@ -1130,7 +1358,7 @@
final int mType;
final BackAnimationAdapter mBackAnimationAdapter;
WindowContainer mCloseTarget;
- WindowContainer mOpenTarget;
+ WindowContainer[] mOpenTargets;
boolean mIsLaunchBehind;
ScheduleAnimationBuilder(int type, BackAnimationAdapter backAnimationAdapter) {
@@ -1138,9 +1366,10 @@
mBackAnimationAdapter = backAnimationAdapter;
}
- ScheduleAnimationBuilder setComposeTarget(WindowContainer close, WindowContainer open) {
+ ScheduleAnimationBuilder setComposeTarget(@NonNull WindowContainer close,
+ @NonNull WindowContainer... open) {
mCloseTarget = close;
- mOpenTarget = open;
+ mOpenTargets = open;
return this;
}
@@ -1150,43 +1379,60 @@
}
boolean containTarget(@NonNull WindowContainer wc) {
- return wc == mOpenTarget || wc == mCloseTarget
- || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc);
+ if (mOpenTargets != null) {
+ for (int i = mOpenTargets.length - 1; i >= 0; --i) {
+ if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) {
+ return true;
+ }
+ }
+ }
+ return wc == mCloseTarget || mCloseTarget.hasChild(wc);
}
/**
* Apply preview strategy on the opening target
+ * @param closeWindow The close window, where it's configuration should cover all
+ * open target(s).
* @param openAnimationAdaptor The animator who can create starting surface.
- * @param visibleOpenActivity The visible activity in opening target.
+ * @param visibleOpenActivities The visible activities in opening targets.
*/
- private void applyPreviewStrategy(BackWindowAnimationAdaptor openAnimationAdaptor,
- ActivityRecord visibleOpenActivity) {
- if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
- openAnimationAdaptor.createStartingSurface();
- return;
+ private void applyPreviewStrategy(@NonNull WindowContainer closeWindow,
+ @NonNull BackWindowAnimationAdaptor[] openAnimationAdaptor,
+ @NonNull ActivityRecord[] visibleOpenActivities) {
+ if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind
+ // TODO (b/274997067) Draw two snapshot in a single starting surface.
+ // We are using TaskId as the key of
+ // StartingSurfaceDrawer#StartingWindowRecordManager, so we cannot create
+ // two activity snapshot with WindowlessStartingWindow.
+ // Try to draw two snapshot within a WindowlessStartingWindow, or find
+ // another key for StartingWindowRecordManager.
+ && openAnimationAdaptor.length == 1) {
+ openAnimationAdaptor[0].createStartingSurface(closeWindow);
+ } else {
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ setLaunchBehind(visibleOpenActivities[i]);
+ }
}
- setLaunchBehind(visibleOpenActivity);
}
- Runnable build() {
- if (mOpenTarget == null || mCloseTarget == null) {
+ @Nullable Runnable build() {
+ if (mOpenTargets == null || mCloseTarget == null || mOpenTargets.length == 0) {
return null;
}
- final ActivityRecord openActivity = mOpenTarget.asTask() != null
- ? mOpenTarget.asTask().getTopNonFinishingActivity()
- : mOpenTarget.asActivityRecord() != null
- ? mOpenTarget.asActivityRecord() : null;
- if (openActivity == null) {
+ final boolean shouldLaunchBehind = mIsLaunchBehind || !isSupportWindowlessSurface();
+ final ActivityRecord[] openingActivities = getTopOpenActivities(mOpenTargets);
+
+ if (shouldLaunchBehind && openingActivities == null) {
Slog.e(TAG, "No opening activity");
return null;
}
- if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
+ if (!composeAnimations(mCloseTarget, mOpenTargets, openingActivities)) {
return null;
}
mCloseTarget.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
- applyPreviewStrategy(mOpenAdaptor, openActivity);
+ applyPreviewStrategy(mCloseTarget, mOpenAnimAdaptor.mAdaptors, openingActivities);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1210,6 +1456,7 @@
// animation was canceled
return;
}
+ mOpenAnimAdaptor.onAnimationFinish();
if (!triggerBack) {
clearBackAnimateTarget();
} else {
@@ -1223,6 +1470,41 @@
}
}
+ /**
+ * Finds next opening activity(ies) based on open targets, which could be:
+ * 1. If the open window is Task, then the open activity can either be an activity, or
+ * two activities inside two TaskFragments
+ * 2. If the open window is Activity, then the open window can be an activity, or two
+ * adjacent TaskFragments below it.
+ */
+ @Nullable
+ private static ActivityRecord[] getTopOpenActivities(
+ @NonNull WindowContainer[] openWindows) {
+ ActivityRecord[] openActivities = null;
+ final WindowContainer mainTarget = openWindows[0];
+ if (mainTarget.asTask() != null) {
+ final ArrayList<ActivityRecord> inTaskActivities = new ArrayList<>();
+ final Task task = mainTarget.asTask();
+ final ActivityRecord tmpPreActivity = task.getTopNonFinishingActivity();
+ if (tmpPreActivity != null) {
+ inTaskActivities.add(tmpPreActivity);
+ findAdjacentActivityIfExist(tmpPreActivity, inTaskActivities);
+ }
+
+ openActivities = new ActivityRecord[inTaskActivities.size()];
+ for (int i = inTaskActivities.size() - 1; i >= 0; --i) {
+ openActivities[i] = inTaskActivities.get(i);
+ }
+ } else if (mainTarget.asActivityRecord() != null) {
+ final int size = openWindows.length;
+ openActivities = new ActivityRecord[size];
+ for (int i = size - 1; i >= 0; --i) {
+ openActivities[i] = openWindows[i].asActivityRecord();
+ }
+ }
+ return openActivities;
+ }
+
private static void setLaunchBehind(@NonNull ActivityRecord activity) {
if (!activity.isVisibleRequested()) {
activity.setVisibility(true);
@@ -1311,7 +1593,7 @@
static TaskSnapshot getSnapshot(@NonNull WindowContainer w) {
if (w.asTask() != null) {
final Task task = w.asTask();
- return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+ return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
task.mTaskId, task.mUserId, false /* restoreFromDisk */,
false /* isLowResolution */);
}
@@ -1340,8 +1622,10 @@
proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress);
proto.write(LAST_BACK_TYPE, mLastBackType);
proto.write(SHOW_WALLPAPER, mShowWallpaper);
- if (mAnimationHandler.mOpenActivity != null) {
- mAnimationHandler.mOpenActivity.writeNameToProto(proto, MAIN_OPEN_ACTIVITY);
+ if (mAnimationHandler.mOpenAnimAdaptor != null
+ && mAnimationHandler.mOpenAnimAdaptor.mAdaptors.length > 0) {
+ mAnimationHandler.mOpenActivities[0].writeNameToProto(
+ proto, MAIN_OPEN_ACTIVITY);
} else {
proto.write(MAIN_OPEN_ACTIVITY, "");
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4a467df..b1c1fd6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1681,8 +1681,8 @@
return false;
}
- if (!StorageManager.isUserKeyUnlocked(mCurrentUser)) {
- // Can't launch home on secondary display areas if device is still locked.
+ if (!StorageManager.isCeStorageUnlocked(mCurrentUser)) {
+ // Can't launch home on secondary display areas if CE storage is still locked.
return false;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 34ae370..e7a1cf1 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -49,6 +49,7 @@
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
+import android.window.RemoteTransition;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
import android.window.TaskFragmentParentInfo;
@@ -566,7 +567,8 @@
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
if (isValidTransaction(wct)) {
- applyTransaction(wct, transitionType, shouldApplyIndependently);
+ applyTransaction(
+ wct, transitionType, shouldApplyIndependently, null /* remoteTransition */);
}
// Even if the transaction is empty, we still need to invoke #onTransactionFinished
// unless the organizer has been unregistered.
@@ -587,14 +589,15 @@
@Override
public void applyTransaction(@NonNull WindowContainerTransaction wct,
- @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
+ @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently,
+ @Nullable RemoteTransition remoteTransition) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
if (!isValidTransaction(wct)) {
return;
}
mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
- shouldApplyIndependently);
+ shouldApplyIndependently, remoteTransition);
}
}
@@ -839,6 +842,7 @@
Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
return false;
}
+
return true;
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 41e49b9..12392a6 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -35,6 +35,7 @@
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -733,7 +734,8 @@
* the task was removed from hierarchy.
*/
int addWindowlessStartingSurface(Task task, ActivityRecord activity, SurfaceControl root,
- TaskSnapshot taskSnapshot, IWindowlessStartingSurfaceCallback callback) {
+ TaskSnapshot taskSnapshot, Configuration configuration,
+ IWindowlessStartingSurfaceCallback callback) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
return INVALID_TASK_ID;
@@ -743,6 +745,7 @@
return INVALID_TASK_ID;
}
final StartingWindowInfo info = task.getStartingWindowInfo(activity);
+ info.taskInfo.configuration.setTo(configuration);
info.taskInfo.taskDescription = activity.taskDescription;
info.taskSnapshot = taskSnapshot;
info.windowlessStartingSurfaceCallback = callback;
@@ -1195,8 +1198,7 @@
}
public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
- if (task == null || !task.isOrganized()
- || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
+ if (!shouldInterceptBackPressedOnRootTask(task)) {
return false;
}
final TaskOrganizerPendingEventsQueue pendingEventsQueue =
@@ -1229,6 +1231,11 @@
return true;
}
+ boolean shouldInterceptBackPressedOnRootTask(Task task) {
+ return task != null && task.isOrganized()
+ && mInterceptBackPressedOnRootTasks.contains(task.mTaskId);
+ }
+
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.print(prefix); pw.println("TaskOrganizerController:");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a8b9417..3497353 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -103,6 +103,7 @@
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
+import android.window.RemoteTransition;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentOperation;
@@ -464,12 +465,20 @@
* transition, which will be queued until the sync engine is
* free if there is any other active sync. If {@code false},
* the {@code wct} will be directly applied to the active sync.
+ * @param remoteTransition {@link RemoteTransition} to apply for the transaction. Only available
+ * for system organizers.
*/
void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
- @WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
+ @WindowManager.TransitionType int type, boolean shouldApplyIndependently,
+ @Nullable RemoteTransition remoteTransition) {
enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
Objects.requireNonNull(wct));
+ if (remoteTransition != null && !mTaskFragmentOrganizerController.isSystemOrganizer(
+ wct.getTaskFragmentOrganizer().asBinder())) {
+ throw new SecurityException(
+ "Only a system organizer is allowed to use remote transition!");
+ }
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
@@ -512,7 +521,7 @@
return;
}
mTransitionController.requestStartTransition(transition, null /* startTask */,
- null /* remoteTransition */, null /* displayChange */);
+ remoteTransition, null /* displayChange */);
transition.setAllReady();
};
mTransitionController.startCollectOrQueue(transition, doApply);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index a4adf58..627461a 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -862,37 +862,41 @@
Slog.i(TAG, "isEnabledCredentialProviderService with componentName: "
+ componentName.flattenToString());
- // TODO(253157366): Check additional set of services.
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
- synchronized (mLock) {
- final List<CredentialManagerServiceImpl> services =
- getServiceListForUserLocked(userId);
- for (CredentialManagerServiceImpl s : services) {
- final ComponentName serviceComponentName = s.getServiceComponentName();
- if (serviceComponentName.equals(componentName)) {
- if (!s.getServicePackageName().equals(callingPackage)) {
- // The component name and the package name do not match.
- MetricUtilities.logApiCalledSimpleV2(
- ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.FAILURE, callingUid);
- Slog.w(
- TAG,
- "isEnabledCredentialProviderService: Component name does "
- + "not match package name.");
- return false;
- }
- MetricUtilities.logApiCalledSimpleV2(
- ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.SUCCESS, callingUid);
- return true;
- }
- }
+ if (componentName == null) {
+ Slog.w(TAG, "isEnabledCredentialProviderService componentName is null");
+ // If the component name was not specified then throw an error and
+ // record a failure because the request failed due to invalid input.
+ MetricUtilities.logApiCalledSimpleV2(
+ ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+ ApiStatus.FAILURE, callingUid);
+ return false;
}
- return false;
+ if (!componentName.getPackageName().equals(callingPackage)) {
+ Slog.w(TAG, "isEnabledCredentialProviderService component name"
+ + " does not match requested component");
+ // If the requested component name package name does not match
+ // the calling package then throw an error and record a failure
+ // metric (because the request failed due to invalid input).
+ MetricUtilities.logApiCalledSimpleV2(
+ ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+ ApiStatus.FAILURE, callingUid);
+ throw new IllegalArgumentException("provided component name does not match"
+ + " does not match requesting component");
+ }
+
+ final Set<ComponentName> enabledProviders = getEnabledProvidersForUser(userId);
+ MetricUtilities.logApiCalledSimpleV2(
+ ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+ ApiStatus.SUCCESS, callingUid);
+ if (enabledProviders == null) {
+ return false;
+ }
+ return enabledProviders.contains(componentName);
}
@Override
diff --git a/services/proguard.flags b/services/proguard.flags
index e11e613..261bb7c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -47,6 +47,11 @@
-keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
-keep,allowoptimization,allowaccessmodification public class com.android.server.net.IpConfigStore { *; }
-keep,allowoptimization,allowaccessmodification public class com.android.server.net.BaseNetworkObserver { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.display.feature.DisplayManagerFlags { *; }
+-keep,allowoptimization,allowaccessmodification class android.app.admin.flags.FeatureFlagsImpl { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; }
+-keep,allowaccessmodification class android.app.admin.flags.Flags { *; }
# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing)
-keep public class com.android.server.utils.Slogf { *; }
@@ -99,9 +104,6 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.input.InputManagerService {
<methods>;
}
--keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl {
- <methods>;
-}
-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbHostManager {
*** usbDeviceRemoved(...);
*** usbDeviceAdded(...);
diff --git a/services/tests/servicestests/res/xml/user_100_v9.xml b/services/tests/servicestests/res/xml/user_100_v9.xml
new file mode 100644
index 0000000..03c08ed
--- /dev/null
+++ b/services/tests/servicestests/res/xml/user_100_v9.xml
@@ -0,0 +1,20 @@
+<user id="100"
+ serialNumber="0"
+ flags="3091"
+ type="android.os.usertype.full.SYSTEM"
+ created="0"
+ lastLoggedIn="0"
+ lastLoggedInFingerprint="0"
+ profileBadge="0">
+ <restrictions no_oem_unlock="true" />
+ <device_policy_local_restrictions>
+ <restrictions_user user_id="0">
+ <restrictions no_camera="true" />
+ </restrictions_user>
+ <restrictions_user user_id="100">
+ <restrictions no_camera="true" />
+ <restrictions no_install_unknown_sources="true" />
+ </restrictions_user>
+ </device_policy_local_restrictions>
+ <ignorePrepareStorageErrors>false</ignorePrepareStorageErrors>
+</user>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index b9e45ba..82efdd3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -535,6 +535,78 @@
@SmallTest
@Test
+ public void testOnClientChange_magnificationTripleTapEnabled_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(true);
+ }
+
+ @SmallTest
+ @Test
+ public void testOnClientChange_magnificationTripleTapDisabled_requestDisconnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(false);
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void testOnClientChange_magnificationTwoFingerTripleTapEnabled_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(true);
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void testOnClientChange_magnificationTwoFingerTripleTapDisabled_requestDisconnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(false);
+ }
+
+ @SmallTest
+ @Test
public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
@@ -547,6 +619,64 @@
verify(mMockWindowMagnificationMgr).requestConnection(true);
}
+ @SmallTest
+ @Test
+ public void testOnClientChange_magnificationTripleTapDisabled_removeMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+ }
+
+ @SmallTest
+ @Test
+ public void testOnClientChange_magnificationTripleTapEnabled_keepMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void onClientChange_magnificationTwoFingerTripleTapDisabled_removeMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void onClientChange_magnificationTwoFingerTripleTapEnabled_keepMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+ }
+
@Test
public void testUnbindIme_whenServiceUnbinds() {
setupAccessibilityServiceConnection(AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 253592c..d1b2e8e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -43,6 +43,7 @@
import android.app.PropertyInvalidatedCache;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.res.Resources;
import android.multiuser.Flags;
import android.os.Looper;
import android.os.Parcel;
@@ -50,21 +51,26 @@
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
+import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.frameworks.servicestests.R;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerService.UserData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.List;
/**
@@ -77,6 +83,7 @@
@MediumTest
public class UserManagerServiceUserInfoTest {
private UserManagerService mUserManagerService;
+ private Resources mResources;
@Before
public void setup() {
@@ -96,6 +103,8 @@
assertEquals("Multiple users so this test can't run.", 1, users.size());
assertEquals("Only user present isn't the system user.",
UserHandle.USER_SYSTEM, users.get(0).id);
+
+ mResources = InstrumentationRegistry.getTargetContext().getResources();
}
@Test
@@ -109,7 +118,7 @@
byte[] bytes = baos.toByteArray();
UserData read = mUserManagerService.readUserLP(
- data.info.id, new ByteArrayInputStream(bytes));
+ data.info.id, new ByteArrayInputStream(bytes), 0);
assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
}
@@ -146,11 +155,13 @@
// Clear the restrictions to see if they are properly read in from the user file.
setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
+ final int userVersion = 10;
//read the secondary and SYSTEM user file to fetch local/global device policy restrictions.
- mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes));
+ mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes),
+ userVersion);
if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
mUserManagerService.readUserLP(UserHandle.USER_SYSTEM,
- new ByteArrayInputStream(systemUserBytes));
+ new ByteArrayInputStream(systemUserBytes), userVersion);
}
assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
@@ -303,6 +314,45 @@
assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
}
+ /** Tests readUserLP upgrading from version 9 to 10+. */
+ @Test
+ public void testUserRestrictionsUpgradeFromV9() throws Exception {
+ final String[] localRestrictions = new String[] {
+ UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ };
+
+ final int userId = 100;
+ UserData data = new UserData();
+ data.info = createUser(userId, FLAG_FULL, "A type");
+
+ mUserManagerService.putUserInfo(data.info);
+
+ for (String restriction : localRestrictions) {
+ assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+ assertFalse(mUserManagerService.hasUserRestriction(restriction, userId));
+ }
+
+ // Convert the xml resource to the system storage xml format.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream os = new DataOutputStream(baos);
+ XmlPullParser in = mResources.getXml(R.xml.user_100_v9);
+ XmlSerializer out = Xml.newBinarySerializer();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+ Xml.copy(in, out);
+ byte[] userBytes = baos.toByteArray();
+ baos.reset();
+
+ final int userVersion = 9;
+ mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(userBytes),
+ userVersion);
+
+ for (String restriction : localRestrictions) {
+ assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+ assertTrue(mUserManagerService.hasUserRestriction(restriction, userId));
+ }
+ }
+
/** Creates a UserInfo with the given flags and userType. */
private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) {
return new UserInfo(userId, "A Name", "A path", flags, userType);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index dd7dec0..7b1fa03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -76,6 +76,7 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -601,30 +602,33 @@
final Task task = createTask(mDefaultDisplay);
final ActivityRecord bottomActivity = createActivityRecord(task);
final ActivityRecord homeActivity = mRootHomeTask.getTopNonFinishingActivity();
-
+ final ArrayList<ActivityRecord> openActivities = new ArrayList<>();
+ openActivities.add(homeActivity);
final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toHomeBuilder =
animationHandler.prepareAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME,
- mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity);
+ mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, openActivities);
assertTrue(toHomeBuilder.mIsLaunchBehind);
toHomeBuilder.build();
- verify(mAtm.mTaskOrganizerController, never())
- .addWindowlessStartingSurface(any(), any(), any(), any(), any());
+ verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface(
+ any(), any(), any(), any(), any(), any());
animationHandler.clearBackAnimateTarget();
+ openActivities.clear();
// Back to ACTIVITY and TASK have the same logic, just with different target.
final ActivityRecord topActivity = createActivityRecord(task);
+ openActivities.add(bottomActivity);
final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toActivityBuilder =
animationHandler.prepareAnimation(
BackNavigationInfo.TYPE_CROSS_ACTIVITY, mBackAnimationAdapter, task, task,
- topActivity, bottomActivity);
+ topActivity, openActivities);
assertFalse(toActivityBuilder.mIsLaunchBehind);
toActivityBuilder.build();
if (preferWindowlessSurface) {
- verify(mAtm.mTaskOrganizerController)
- .addWindowlessStartingSurface(any(), any(), any(), any(), any());
+ verify(mAtm.mTaskOrganizerController).addWindowlessStartingSurface(
+ any(), any(), any(), any(), any(), any());
} else {
- verify(mAtm.mTaskOrganizerController, never())
- .addWindowlessStartingSurface(any(), any(), any(), any(), any());
+ verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface(
+ any(), any(), any(), any(), any(), any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index c241033..eb78906 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -872,7 +872,7 @@
new TestDisplayContent.Builder(mAtm, 1000, 1500)
.setSystemDecorations(true).build();
- // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
+ // Use invalid user id to let StorageManager.isCeStorageUnlocked() return false.
final int currentUser = mRootWindowContainer.mCurrentUser;
mRootWindowContainer.mCurrentUser = -1;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index c57b051..8a90f12 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -86,7 +86,9 @@
import android.platform.test.annotations.Presubmit;
import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
import android.window.ITaskFragmentOrganizer;
+import android.window.RemoteTransition;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
@@ -546,6 +548,35 @@
}
@Test
+ public void testApplyTransaction_disallowRemoteTransitionForNonSystemOrganizer() {
+ mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ // Throw exception if the transaction has remote transition and is not requested by system
+ // organizer
+ assertThrows(SecurityException.class, () ->
+ mController.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+ true /* shouldApplyIndependently */,
+ new RemoteTransition(mock(IRemoteTransition.class))));
+ }
+
+ @Test
+ public void testApplyTransaction_allowRemoteTransitionForSystemOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+
+ mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ // Remote transition is allowed for system organizer
+ mController.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+ true /* shouldApplyIndependently */,
+ new RemoteTransition(mock(IRemoteTransition.class)));
+ }
+
+ @Test
public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() {
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
@@ -1801,13 +1832,13 @@
private void assertApplyTransactionDisallowed(WindowContainerTransaction t) {
assertThrows(SecurityException.class, () ->
mController.applyTransaction(t, TASK_FRAGMENT_TRANSIT_CHANGE,
- false /* shouldApplyIndependently */));
+ false /* shouldApplyIndependently */, null /* remoteTransition */));
}
/** Asserts that applying the given transaction will not throw any exception. */
private void assertApplyTransactionAllowed(WindowContainerTransaction t) {
mController.applyTransaction(t, TASK_FRAGMENT_TRANSIT_CHANGE,
- false /* shouldApplyIndependently */);
+ false /* shouldApplyIndependently */, null /* remoteTransition */);
}
/** Asserts that there will be a transaction for TaskFragment appeared. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 699580a..2c39173 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -614,7 +614,7 @@
t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
- false /* shouldApplyIndependently */);
+ false /* shouldApplyIndependently */, null /* remoteTransition */);
// Should be not visible and not focusable after the transaction.
assertFalse(taskFragment.shouldBeVisible(null));
@@ -628,7 +628,7 @@
t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
- false /* shouldApplyIndependently */);
+ false /* shouldApplyIndependently */, null /* remoteTransition */);
// Should be visible and focusable after the transaction.
assertTrue(taskFragment.shouldBeVisible(null));
@@ -680,7 +680,7 @@
assertThrows(SecurityException.class, () ->
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
- false /* shouldApplyIndependently */)
+ false /* shouldApplyIndependently */, null /* remoteTransition */)
);
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 1f32c97..55fecfc 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -54,6 +55,7 @@
import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ClientTransactionalServiceWrapper;
import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -412,6 +414,14 @@
"android.telecom.extra.CALL_CREATED_TIME_MILLIS";
/**
+ * The extra for call log uri that was used to mark missed calls as read when dialer gets the
+ * notification on reboot.
+ */
+ @FlaggedApi(Flags.FLAG_ADD_CALL_URI_FOR_MISSED_CALLS)
+ public static final String EXTRA_CALL_LOG_URI =
+ "android.telecom.extra.CALL_LOG_URI";
+
+ /**
* Optional extra for incoming containing a long which specifies the time the
* call was answered by user. This value is in milliseconds.
* @hide
@@ -2361,6 +2371,11 @@
* <p>
* <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
* the call is placed in order for the notification to be non-dismissible.
+ * <p><b>Note</b>: Call Forwarding MMI codes can only be dialed by applications that are
+ * configured as the user defined default dialer or system dialer role. If a call containing a
+ * call forwarding MMI code is placed by an application that is not in one of these roles, the
+ * dialer will be launched with a UI showing the MMI code already populated so that the user can
+ * confirm the action before the call is placed.
* @param address The address to make the call to.
* @param extras Bundle of extras to use with the call.
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 042b2a3..4250bd1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9692,6 +9692,7 @@
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c0d6b30..e9ea5a7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17483,9 +17483,8 @@
* {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
* and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SLICING_ADDITIONAL_ERROR_CODES)
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
/**
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index a20266a..28eab8f 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -20,7 +20,6 @@
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.android.lint.aidl.EnforcePermissionDetector
-import com.google.android.lint.aidl.EnforcePermissionHelperDetector
import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
import com.google.auto.service.AutoService
@@ -30,7 +29,8 @@
override val issues = listOf(
EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
- EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION,
SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
)
@@ -45,4 +45,4 @@
feedbackUrl = "http://b/issues/new?component=315013",
contact = "repsonsible-apis@google.com"
)
-}
\ No newline at end of file
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 3a95df9..dcd94f1 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -30,31 +30,34 @@
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.findCallExpression
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiArrayInitializerMemberValue
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.skipParenthesizedExprDown
import java.util.EnumSet
/**
- * Lint Detector that ensures that any method overriding a method annotated
- * with @EnforcePermission is also annotated with the exact same annotation.
- * The intent is to surface the effective permission checks to the service
- * implementations.
+ * Lint Detector that ensures consistency when using the @EnforcePermission
+ * annotation. Multiple verifications are implemented:
*
- * This is done with 2 mechanisms:
* 1. Visit any annotation usage, to ensure that any derived class will have
- * the correct annotation on each methods. This is for the top to bottom
- * propagation.
- * 2. Visit any annotation, to ensure that if a method is annotated, it has
+ * the correct annotation on each methods. Even if the subclass does not
+ * have the annotation, visitAnnotationUsage will be called which allows us
+ * to capture the issue.
+ * 2. Visit any method, to ensure that if a method is annotated, it has
* its ancestor also annotated. This is to avoid having an annotation on a
* Java method without the corresponding annotation on the AIDL interface.
+ * 3. When annotated, ensures that the first instruction is to call the helper
+ * method (or the parent helper).
*/
class EnforcePermissionDetector : Detector(), SourceCodeScanner {
@@ -62,9 +65,8 @@
return listOf(ANNOTATION_ENFORCE_PERMISSION)
}
- override fun getApplicableUastTypes(): List<Class<out UElement>> {
- return listOf(UAnnotation::class.java)
- }
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(UMethod::class.java)
private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
if (elem is PsiArrayInitializerMemberValue)
@@ -129,11 +131,6 @@
overriddenMethod: PsiMethod,
checkEquivalence: Boolean = true
) {
- // If method is not from a Stub subclass, this method shouldn't use @EP at all.
- // This is handled by EnforcePermissionHelperDetector.
- if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) {
- return
- }
val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
val location = context.getLocation(element)
@@ -169,40 +166,102 @@
) {
if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
annotationInfo.origin == AnnotationOrigin.METHOD) {
+ /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */
+ val uMethod = element as? UMethod ?: return
+ if (!isContainedInSubclassOfStub(context, uMethod)) {
+ return
+ }
val overridingMethod = element.sourcePsi as PsiMethod
val overriddenMethod = usageInfo.referenced as PsiMethod
compareMethods(context, element, overridingMethod, overriddenMethod)
}
}
- override fun createUastHandler(context: JavaContext): UElementHandler {
- return object : UElementHandler() {
- override fun visitAnnotation(node: UAnnotation) {
- if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
- return
- }
- val method = node.uastParent as? UMethod ?: return
- val overridingMethod = method as PsiMethod
- val parents = overridingMethod.findSuperMethods()
- for (overriddenMethod in parents) {
- // The equivalence check can be skipped, if both methods are
- // annotated, it will be verified by visitAnnotationUsage.
- compareMethods(context, method, overridingMethod,
- overriddenMethod, checkEquivalence = false)
- }
+ override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+ private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (context.evaluator.isAbstract(node)) return
+ if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+ if (!isContainedInSubclassOfStub(context, node)) {
+ context.report(
+ ISSUE_MISUSING_ENFORCE_PERMISSION,
+ node,
+ context.getLocation(node),
+ "The class of ${node.name} does not inherit from an AIDL generated Stub class"
+ )
+ return
+ }
+
+ /* Check that we are connected to the super class */
+ val overridingMethod = node as PsiMethod
+ val parents = overridingMethod.findSuperMethods()
+ for (overriddenMethod in parents) {
+ // The equivalence check can be skipped, if both methods are
+ // annotated, it will be verified by visitAnnotationUsage.
+ compareMethods(context, node, overridingMethod,
+ overriddenMethod, checkEquivalence = false)
+ }
+
+ /* Check that the helper is called as a first instruction */
+ val targetExpression = getHelperMethodCallSourceString(node)
+ val message =
+ "Method must start with $targetExpression or super.${node.name}(), if applicable"
+
+ val firstExpression = (node.uastBody as? UBlockExpression)
+ ?.expressions?.firstOrNull()
+
+ if (firstExpression == null) {
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ message,
+ )
+ return
+ }
+
+ val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
+ .asSourceString()
+ .filterNot(Char::isWhitespace)
+
+ if (firstExpressionSource != targetExpression &&
+ firstExpressionSource != "super.$targetExpression") {
+ // calling super.<methodName>() is also legal
+ val directSuper = context.evaluator.getSuperMethod(node)
+ val firstCall = findCallExpression(firstExpression)?.resolve()
+ if (directSuper != null && firstCall == directSuper) return
+
+ val locationTarget = getLocationTarget(firstExpression)
+ val expressionLocation = context.getLocation(locationTarget)
+
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ message,
+ getHelperMethodFix(node, expressionLocation),
+ )
}
}
}
companion object {
+
+ private const val HELPER_SUFFIX = "_enforcePermission"
+
val EXPLANATION = """
- The @EnforcePermission annotation is used to indicate that the underlying binder code
- has already verified the caller's permissions before calling the appropriate method. The
- verification code is usually generated by the AIDL compiler, which also takes care of
- annotating the generated Java code.
+ The @EnforcePermission annotation is used to delegate the verification of the caller's
+ permissions to a generated AIDL method.
In order to surface that information to platform developers, the same annotation must be
used on the implementation class or methods.
+
+ The @EnforcePermission annotation can only be used on methods whose class extends from
+ the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
+ AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
+
+ yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
+ either call it directly, or call it indirectly via super.yourMethodName().
"""
val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
@@ -230,5 +289,44 @@
EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
+
+ val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+ id = "MissingEnforcePermissionHelper",
+ briefDescription = """Missing permission-enforcing method call in AIDL method
+ |annotated with @EnforcePermission""".trimMargin(),
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EnforcePermissionDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+
+ val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
+ id = "MisusingEnforcePermissionAnnotation",
+ briefDescription = "@EnforcePermission cannot be used here",
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EnforcePermissionDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+
+ /**
+ * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+ * resulting in an incorrect Location if used directly
+ */
+ private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+ if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+ if (firstExpression is UDeclarationsExpression) {
+ return firstExpression.declarations.firstOrNull()?.sourcePsi
+ }
+ return null
+ }
}
}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
deleted file mode 100644
index 758de4d..0000000
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.google.android.lint.findCallExpression
-import com.intellij.psi.PsiElement
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UDeclarationsExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.skipParenthesizedExprDown
-
-import java.util.EnumSet
-
-class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
- override fun getApplicableUastTypes(): List<Class<out UElement?>> =
- listOf(UMethod::class.java)
-
- override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
- private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
- override fun visitMethod(node: UMethod) {
- if (context.evaluator.isAbstract(node)) return
- if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
-
- if (!isContainedInSubclassOfStub(context, node)) {
- context.report(
- ISSUE_MISUSING_ENFORCE_PERMISSION,
- node,
- context.getLocation(node),
- "The class of ${node.name} does not inherit from an AIDL generated Stub class"
- )
- return
- }
-
- val targetExpression = getHelperMethodCallSourceString(node)
- val message =
- "Method must start with $targetExpression or super.${node.name}(), if applicable"
-
- val firstExpression = (node.uastBody as? UBlockExpression)
- ?.expressions?.firstOrNull()
-
- if (firstExpression == null) {
- context.report(
- ISSUE_ENFORCE_PERMISSION_HELPER,
- context.getLocation(node),
- message,
- )
- return
- }
-
- val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
- .asSourceString()
- .filterNot(Char::isWhitespace)
-
- if (firstExpressionSource != targetExpression &&
- firstExpressionSource != "super.$targetExpression") {
- // calling super.<methodName>() is also legal
- val directSuper = context.evaluator.getSuperMethod(node)
- val firstCall = findCallExpression(firstExpression)?.resolve()
- if (directSuper != null && firstCall == directSuper) return
-
- val locationTarget = getLocationTarget(firstExpression)
- val expressionLocation = context.getLocation(locationTarget)
-
- context.report(
- ISSUE_ENFORCE_PERMISSION_HELPER,
- context.getLocation(node),
- message,
- getHelperMethodFix(node, expressionLocation),
- )
- }
- }
- }
-
- companion object {
- private const val HELPER_SUFFIX = "_enforcePermission"
-
- private const val EXPLANATION = """
- The @EnforcePermission annotation can only be used on methods whose class extends from
- the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
- AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
-
- yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
- either call it directly, or call it indirectly via super.yourMethodName().
- """
-
- val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
- id = "MissingEnforcePermissionHelper",
- briefDescription = """Missing permission-enforcing method call in AIDL method
- |annotated with @EnforcePermission""".trimMargin(),
- explanation = EXPLANATION,
- category = Category.SECURITY,
- priority = 6,
- severity = Severity.ERROR,
- implementation = Implementation(
- EnforcePermissionHelperDetector::class.java,
- EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
- )
- )
-
- val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
- id = "MisusingEnforcePermissionAnnotation",
- briefDescription = "@EnforcePermission cannot be used here",
- explanation = EXPLANATION,
- category = Category.SECURITY,
- priority = 6,
- severity = Severity.ERROR,
- implementation = Implementation(
- EnforcePermissionHelperDetector::class.java,
- EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
- )
- )
-
- /**
- * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
- * resulting in an incorrect Location if used directly
- */
- private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
- if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
- if (firstExpression is UDeclarationsExpression) {
- return firstExpression.declarations.firstOrNull()?.sourcePsi
- }
- return null
- }
- }
-}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
index 5a63bb4..3ef02f8 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
@@ -25,10 +25,10 @@
@Suppress("UnstableApiUsage")
class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() {
- override fun getDetector(): Detector = EnforcePermissionHelperDetector()
+ override fun getDetector(): Detector = EnforcePermissionDetector()
override fun getIssues(): List<Issue> = listOf(
- EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER
+ EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER
)
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
index 10a6e1d..64e2bfb 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -20,10 +20,10 @@
import com.android.tools.lint.checks.infrastructure.TestLintTask
class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
- override fun getDetector() = EnforcePermissionHelperDetector()
+ override fun getDetector() = EnforcePermissionDetector()
override fun getIssues() = listOf(
- EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
- EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
+ EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
)
override fun lint(): TestLintTask = super.lint().allowMissingSdk()