Merge "Auto Add/Remove a11y tiles when the ACCESSIBILITY_QS_TILES updates" into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index de6f023..5d65d9d 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,13 @@
package: "com.android.server.job"
flag {
+ name: "do_not_force_rush_execution_at_boot"
+ namespace: "backstage_power"
+ description: "Don't force rush job execution right after boot completion"
+ bug: "321598070"
+}
+
+flag {
name: "relax_prefetch_connectivity_constraint_only_on_charger"
namespace: "backstage_power"
description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 31214cb..696c317 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -3919,6 +3919,7 @@
if (locationManager != null
&& locationManager.getProvider(LocationManager.FUSED_PROVIDER)
!= null) {
+ mHasFusedLocation = true;
locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
mLocationRequest,
AppSchedulingModuleThread.getExecutor(),
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 b0f378d..7a92cca 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2720,8 +2720,10 @@
sc.maybeStartTrackingJobLocked(job, null);
}
});
- // GO GO GO!
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ if (!Flags.doNotForceRushExecutionAtBoot()) {
+ // GO GO GO!
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
}
}
}
@@ -5441,6 +5443,8 @@
pw.println("Aconfig flags:");
pw.increaseIndent();
+ pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
+ Flags.doNotForceRushExecutionAtBoot());
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index c14efae..6f2393a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -344,11 +344,14 @@
final String flagName = getNextArgRequired();
switch (flagName) {
+ case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
+ pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ break;
case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
pw.println(android.app.job.Flags.jobDebugInfoApis());
break;
- case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
- pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
+ pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
diff --git a/core/api/current.txt b/core/api/current.txt
index adb4eac..c92b4ff 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1604,6 +1604,7 @@
field public static final int switchTextOff = 16843628; // 0x101036c
field public static final int switchTextOn = 16843627; // 0x101036b
field public static final int syncable = 16842777; // 0x1010019
+ field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly;
field public static final int tabStripEnabled = 16843453; // 0x10102bd
field public static final int tabStripLeft = 16843451; // 0x10102bb
field public static final int tabStripRight = 16843452; // 0x10102bc
@@ -7700,7 +7701,7 @@
method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
- method public android.app.WallpaperInfo getWallpaperInfo();
+ method @RequiresPermission(value="QUERY_ALL_PACKAGES", conditional=true) public android.app.WallpaperInfo getWallpaperInfo();
method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
method public boolean hasResourceWallpaper(@RawRes int);
method public boolean isSetWallpaperAllowed();
@@ -22195,11 +22196,10 @@
@FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable {
method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
- method public void close();
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") public void close();
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec);
- method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release();
method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
}
@@ -36622,6 +36622,7 @@
field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
@@ -41702,10 +41703,10 @@
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+ method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
- method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
}
@@ -52463,9 +52464,9 @@
field protected static final int[] PRESSED_STATE_SET;
field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET;
field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0.0f;
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120.0f;
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30.0f;
- field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60.0f;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4.0f;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2.0f;
+ field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3.0f;
field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1.0f;
field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION;
field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION_X;
@@ -57119,7 +57120,7 @@
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method @Deprecated public abstract boolean getDatabaseEnabled();
+ method public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57165,7 +57166,7 @@
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method @Deprecated public abstract void setDatabaseEnabled(boolean);
+ method public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
@@ -59296,6 +59297,7 @@
ctor public RemoteViews(@NonNull java.util.Map<android.util.SizeF,android.widget.RemoteViews>);
ctor public RemoteViews(android.widget.RemoteViews);
ctor public RemoteViews(android.os.Parcel);
+ ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews(@NonNull android.widget.RemoteViews.DrawInstructions);
method public void addStableView(@IdRes int, @NonNull android.widget.RemoteViews, int);
method public void addView(@IdRes int, android.widget.RemoteViews);
method public android.view.View apply(android.content.Context, android.view.ViewGroup);
@@ -59404,6 +59406,15 @@
ctor public RemoteViews.ActionException(String);
}
+ @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
+ method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
+ }
+
+ @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
+ ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews.DrawInstructions.Builder(@NonNull java.util.List<byte[]>);
+ method @FlaggedApi("android.appwidget.flags.draw_data_parcel") @NonNull public android.widget.RemoteViews.DrawInstructions build();
+ }
+
public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
method public int describeContents();
method public int getItemCount();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 318badf..ff2279c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -22,7 +22,7 @@
field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
- field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
+ field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
@@ -190,6 +190,7 @@
field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
+ field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e288b42..1bdbd4c5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6792,6 +6792,7 @@
}
}
if (killApp) {
+ // Keep in sync with "perhaps it was removed" case below.
mPackages.remove(packages[i]);
mResourcePackages.remove(packages[i]);
}
@@ -6859,6 +6860,12 @@
}
} catch (RemoteException e) {
}
+ } else {
+ // No package, perhaps it was removed?
+ Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ + " but missing application info. Assuming REMOVED.");
+ mPackages.remove(packages[i]);
+ mResourcePackages.remove(packages[i]);
}
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d8d136a..ccd8456 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2958,7 +2958,8 @@
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
- .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+ // will make it an app-op permission in the future.
+ // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
};
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index 1f5f2e4..5dd7ab0 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -69,6 +69,11 @@
public HomeVisibilityListener() {
mObserver = new android.app.IProcessObserver.Stub() {
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid,
+ String packageName, String processName) {
+ }
+
+ @Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
refreshHomeVisibility();
}
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
index 7be3620..5c5e72c 100644
--- a/core/java/android/app/IProcessObserver.aidl
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -18,6 +18,17 @@
/** {@hide} */
oneway interface IProcessObserver {
+ /**
+ * Invoked when an app process starts up.
+ *
+ * @param pid The pid of the process.
+ * @param processUid The UID associated with the process.
+ * @param packageUid The UID associated with the package.
+ * @param packageName The name of the package.
+ * @param processName The name of the process.
+ */
+ void onProcessStarted(int pid, int processUid, int packageUid,
+ @utf8InCpp String packageName, @utf8InCpp String processName);
void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
void onForegroundServicesChanged(int pid, int uid, int serviceTypes);
void onProcessDied(int pid, int uid);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 36b03c1..0116ca2 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1899,15 +1899,22 @@
/**
* Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+ * caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * In order to use this, apps should declare a {@code <queries>} tag with the action
- * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * Before Android U, this method requires the
+ * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
+ * </p>
+ *
+ * <p>
+ * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag
+ * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
* this method will return {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*/
+ @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true)
public WallpaperInfo getWallpaperInfo() {
return getWallpaperInfoForUser(mContext.getUserId());
}
@@ -1924,19 +1931,14 @@
}
/**
- * Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+ * Returns the information about the designated wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if
* the caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * Before Android U, this method requires the
- * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
- * </p>
- *
- * <p>
- * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag
- * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
- * this method will return {@code null} if the caller doesn't otherwise have
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return
+ * {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*
@@ -1952,7 +1954,7 @@
/**
* Returns the information about the designated wallpaper if its current wallpaper is a live
* wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
- * the caller doesn't have the appropriate permissions, this returns {@code null}.
+ * caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
* In order to use this, apps should declare a {@code <queries>} tag
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 325aa28f..83e18ec 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -133,4 +133,10 @@
* device.
*/
boolean isVirtualDeviceOwnedMirrorDisplay(int displayId);
+
+ /**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ */
+ List<String> getAllPersistentDeviceIds();
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c7a75ed..e9b94c9 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -41,6 +41,7 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -146,6 +147,7 @@
private boolean mExported;
private boolean mNoPerms;
private boolean mSingleUser;
+ private boolean mSystemUserOnly;
private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray();
private ThreadLocal<AttributionSource> mCallingAttributionSource;
@@ -377,7 +379,9 @@
!= PermissionChecker.PERMISSION_GRANTED
&& getContext().checkUriPermission(userUri, Binder.getCallingPid(),
callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PackageManager.PERMISSION_GRANTED) {
+ != PackageManager.PERMISSION_GRANTED
+ && !deniedAccessSystemUserOnlyProvider(callingUserId,
+ mSystemUserOnly)) {
FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
enumCheckUriPermission,
callingUid, uri.getAuthority(), type);
@@ -865,6 +869,10 @@
boolean checkUser(int pid, int uid, Context context) {
final int callingUserId = UserHandle.getUserId(uid);
+ if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+ return false;
+ }
+
if (callingUserId == context.getUserId() || mSingleUser) {
return true;
}
@@ -987,6 +995,9 @@
// last chance, check against any uri grants
final int callingUserId = UserHandle.getUserId(uid);
+ if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
? maybeAddUserId(uri, callingUserId) : uri;
if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -2623,6 +2634,7 @@
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
+ mSystemUserOnly = (info.flags & ProviderInfo.FLAG_SYSTEM_USER_ONLY) != 0;
setAuthorities(info.authority);
}
if (Build.IS_DEBUGGABLE) {
@@ -2756,6 +2768,11 @@
String auth = uri.getAuthority();
if (!mSingleUser) {
int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+ if (deniedAccessSystemUserOnlyProvider(mContext.getUserId(),
+ mSystemUserOnly)) {
+ throw new SecurityException("Trying to query a SYSTEM user only content"
+ + " provider from user:" + mContext.getUserId());
+ }
if (userId != UserHandle.USER_CURRENT
&& userId != mContext.getUserId()
// Since userId specified in content uri, the provider userId would be
@@ -2929,4 +2946,16 @@
Trace.traceBegin(traceTag, methodName + subInfo);
}
}
+ /**
+ * Return true if access to content provider is denied because it's a SYSTEM user only
+ * provider and the calling user is not the SYSTEM user.
+ *
+ * @param callingUserId UserId of the caller accessing the content provider.
+ * @param systemUserOnly true when the content provider is only available for the SYSTEM user.
+ */
+ private static boolean deniedAccessSystemUserOnlyProvider(int callingUserId,
+ boolean systemUserOnly) {
+ return Flags.enableSystemUserOnlyForServicesAndProviders()
+ && (callingUserId != UserHandle.USER_SYSTEM && systemUserOnly);
+ }
}
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 9e553db..de33fa8 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -89,6 +89,15 @@
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
+ * Bit in {@link #flags}: If set, this provider will only be available
+ * for the system user.
+ * Set from the android.R.attr#systemUserOnly attribute.
+ * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+ * @hide
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+ /**
* Bit in {@link #flags}: If set, a single instance of the provider will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index ae46c027..2b378b1 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -101,6 +101,14 @@
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
+ * @hide Bit in {@link #flags}: If set, this service will only be available
+ * for the system user.
+ * Set from the android.R.attr#systemUserOnly attribute.
+ * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+ /**
* Bit in {@link #flags}: If set, a single instance of the service will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index e4e9fba..903875b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -153,3 +153,11 @@
bug: "291135724"
is_fixed_read_only: true
}
+
+flag {
+ name: "fix_system_apps_first_install_time"
+ namespace: "package_manager_service"
+ description: "Feature flag to fix the first-install timestamps for system apps."
+ bug: "321258605"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5bfc012..9644d80 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -84,4 +84,11 @@
namespace: "profile_experiences"
description: "Enable auto-locking private space on device restarts"
bug: "296993385"
-}
\ No newline at end of file
+}
+flag {
+ name: "enable_system_user_only_for_services_and_providers"
+ namespace: "multiuser"
+ description: "Enable systemUserOnly manifest attribute for services and providers."
+ bug: "302354856"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 796a57b..2e63664 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.IntentSender;
import android.os.Binder;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
@@ -58,6 +59,9 @@
@SystemService(Context.CREDENTIAL_SERVICE)
public final class CredentialManager {
private static final String TAG = "CredentialManager";
+ private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
/** @hide */
@IntDef(
@@ -757,9 +761,7 @@
public void onPendingIntent(PendingIntent pendingIntent) {
try {
mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
- ActivityOptions.makeBasic()
- .setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
@@ -817,7 +819,8 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 212f571..75d671b 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,9 +22,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
import android.util.Log;
@@ -41,6 +43,10 @@
*/
public final class PrepareGetCredentialResponse {
+ private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
+
/**
* A handle that represents a pending get-credential operation. Pass this handle to {@link
* CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
@@ -80,7 +86,8 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent for show()", e);
executor.execute(() -> callback.onError(
@@ -101,7 +108,8 @@
});
try {
- context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0);
+ context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0,
+ OPTIONS_SENDER_BAL_OPTIN);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent for show()", e);
executor.execute(() -> callback.onError(
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index 73ac333..d51e62e 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -33,4 +33,20 @@
* Defines behavior in response to authentication stopping
*/
void onAuthenticationStopped();
+
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationSucceeded(int requestReason, int userId);
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationFailed(int requestReason, int userId);
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e267e6b..8e234fa 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
*/
package android.hardware.face;
+import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricStateListener;
@@ -181,6 +182,14 @@
// authenticators. The callback is automatically removed after it's invoked.
void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+ // Registers AuthenticationStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+ // Unregisters AuthenticationStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 8644d91..f65b713 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -32,6 +32,7 @@
* @hide
*/
@SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class LogMaker {
private static final String TAG = "LogBuilder";
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 365f913..594ec18 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,6 +16,7 @@
package android.net;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ActivityManager.procStateToString;
import static android.content.pm.PackageManager.GET_SIGNATURES;
@@ -170,6 +171,8 @@
public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
/** @hide */
public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
+ /** @hide */
+ public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
@@ -180,6 +183,9 @@
/** @hide */
public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
+ /** @hide */
+ public static final int BACKGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
/**
* {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
* applies to.
@@ -264,6 +270,16 @@
* @hide
*/
public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+
+ /**
+ * Flag to indicate that the app is exempt from always-on background network restrictions.
+ * Note that this is explicitly different to the flag NOT_FOREGROUND which is used to grant
+ * shared exception to apps from power restrictions like doze, battery saver and app-standby.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_NOT_IN_BACKGROUND = 1 << 7;
+
/**
* Flag to indicate that app is exempt from certain metered network restrictions because user
* explicitly exempted it.
@@ -822,6 +838,21 @@
}
/**
+ * This is currently only used as an implementation detail for
+ * {@link com.android.server.net.NetworkPolicyManagerService}.
+ * Only put here to be together with other isProcStateAllowed* methods.
+ *
+ * @hide
+ */
+ public static boolean isProcStateAllowedNetworkWhileBackground(@Nullable UidState uidState) {
+ if (uidState == null) {
+ return false;
+ }
+ return uidState.procState < BACKGROUND_THRESHOLD_STATE
+ || (uidState.capability & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0;
+ }
+
+ /**
* Returns true if {@param procState} is considered foreground and as such will be allowed
* to access network when the device is in data saver mode. Otherwise, false.
* @hide
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5871717..3977bdf 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.sysprop.DeviceProperties;
import android.sysprop.SocProperties;
import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@
/**
* Information about the current build, extracted from system properties.
*/
+@RavenwoodKeepWholeClass
public class Build {
private static final String TAG = "Build";
@@ -307,7 +309,7 @@
* compatibility.
*/
final String[] abiList;
- if (VMRuntime.getRuntime().is64Bit()) {
+ if (android.os.Process.is64Bit()) {
abiList = SUPPORTED_64_BIT_ABIS;
} else {
abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2..a818919 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
import android.util.Log;
import android.util.MutableInt;
@@ -36,6 +38,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
/**
* Gives access to the system properties store. The system properties
@@ -51,6 +55,8 @@
* {@hide}
*/
@SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
public class SystemProperties {
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@
}
}
+ /** @hide */
+ public static void init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+ native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+ SystemProperties::callChangeCallbacks);
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ /** @hide */
+ public static void reset$ravenwood() {
+ native_reset$ravenwood();
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ // These native methods are currently only implemented by Ravenwood, as it's the only
+ // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+ private static native void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback);
+ private static native void native_reset$ravenwood();
+
// The one-argument version of native_get used to be a regular native function. Nowadays,
// we use the two-argument form of native_get all the time, but we can't just delete the
// one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 5d7e04d..c0b4909 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -36,6 +36,7 @@
* href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
* with Systrace</a>.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class Trace {
/*
* Writes trace events to the kernel trace buffer. These trace events can be
@@ -123,10 +124,26 @@
@UnsupportedAppUsage
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long nativeGetEnabledTags();
+ @android.ravenwood.annotation.RavenwoodReplace
private static native void nativeSetAppTracingAllowed(boolean allowed);
+ @android.ravenwood.annotation.RavenwoodReplace
private static native void nativeSetTracingEnabled(boolean allowed);
+ private static long nativeGetEnabledTags$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
+ // Tracing currently completely disabled under Ravenwood
+ }
+
+ private static void nativeSetTracingEnabled$ravenwood(boolean allowed) {
+ // Tracing currently completely disabled under Ravenwood
+ }
+
@FastNative
private static native void nativeTraceCounter(long tag, String name, long value);
@FastNative
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d946430..ecd6f22 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -138,6 +138,20 @@
public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
/**
+ * Activity Action: Show settings to provide guide about carrier satellite messaging.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
+
+ /**
* Activity Action: Show settings to allow configuration of APNs.
* <p>
* Input: Nothing.
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1994058..43163b3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -58,3 +58,10 @@
bug: "290312729"
is_fixed_read_only: true
}
+
+flag {
+ name: "report_primary_auth_attempts"
+ namespace: "biometrics"
+ description: "Report primary auth attempts from LockSettingsService"
+ bug: "285053096"
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 76e0c25..bbda068 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -271,7 +271,6 @@
boolean mDrawingAllowed;
boolean mOffsetsChanged;
boolean mFixedSizeAllowed;
- boolean mShouldDim;
// Whether the wallpaper should be dimmed by default (when no additional dimming is applied)
// based on its color hints
boolean mShouldDimByDefault;
@@ -348,9 +347,11 @@
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
- private float mWallpaperDimAmount = 0.05f;
+
+ private float mCustomDimAmount = 0f;
+ private float mWallpaperDimAmount = 0f;
private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
- private float mDefaultDimAmount = mWallpaperDimAmount;
+ private float mDefaultDimAmount = 0.05f;
SurfaceControl mSurfaceControl = new SurfaceControl();
SurfaceControl mBbqSurfaceControl;
@@ -986,11 +987,8 @@
mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
&& (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0);
- // If default dimming value changes and no additional dimming is applied
- if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) {
- mShouldDim = mShouldDimByDefault;
- updateSurfaceDimming();
- }
+ // Recompute dim in case it changed compared to the previous WallpaperService
+ updateWallpaperDimming(mCustomDimAmount);
}
/**
@@ -999,28 +997,21 @@
* @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
*/
private void updateWallpaperDimming(float dimAmount) {
- if (dimAmount == mWallpaperDimAmount) {
- return;
- }
+ mCustomDimAmount = Math.min(1f, dimAmount);
- // Custom dim amount cannot be less than the default dim amount.
- mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
- // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
- // based on its default wallpaper color hints.
- mShouldDim = dimAmount != 0f || mShouldDimByDefault;
- updateSurfaceDimming();
- }
+ // If default dim is enabled, the actual dim amount is at least the default dim amount
+ mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
+ : Math.max(mDefaultDimAmount, mCustomDimAmount);
- private void updateSurfaceDimming() {
- if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
+ if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+ || mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
return;
}
SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
- if ((!isPreview() && mShouldDim)
- || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
+ if (!isPreview()) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
// Animate dimming to gradually change the wallpaper alpha from the previous
@@ -1545,8 +1536,6 @@
.createWindowContext(TYPE_WALLPAPER, null /* options */);
mDefaultDimAmount = mDisplayContext.getResources().getFloat(
com.android.internal.R.dimen.config_wallpaperDimAmount);
- mWallpaperDimAmount = mDefaultDimAmount;
- mPreviousWallpaperDimAmount = mWallpaperDimAmount;
mDisplayState = mDisplay.getCommittedState();
mMergedConfiguration.setOverrideConfiguration(
mDisplayContext.getResources().getConfiguration());
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 7b9cb6a..9286049 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -40,6 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import com.android.text.flags.Flags;
import java.lang.ref.WeakReference;
@@ -1276,8 +1277,21 @@
}
public void onSpanRemoved(Spannable s, Object o, int start, int end) {
- if (o instanceof UpdateLayout)
- transformAndReflow(s, start, end);
+ if (o instanceof UpdateLayout) {
+ if (Flags.insertModeCrashWhenDelete()) {
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ // It's possible that a Span is removed when the text covering it is
+ // deleted, in this case, the original start and end of the span might be
+ // OOB. So it'll reflow the entire string instead.
+ reflow(s, 0, 0, s.length());
+ } else {
+ reflow(s, start, end - start, end - start);
+ }
+ } else {
+ transformAndReflow(s, start, end);
+ }
+ }
}
public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
@@ -1287,8 +1301,21 @@
// instead of causing an exception
start = 0;
}
- transformAndReflow(s, start, end);
- transformAndReflow(s, nstart, nend);
+ if (Flags.insertModeCrashWhenDelete()) {
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ // When text is changed, it'll also trigger onSpanChanged. In this case we
+ // can't determine the updated range in the transformed text. So it'll
+ // reflow the entire range instead.
+ reflow(s, 0, 0, s.length());
+ } else {
+ reflow(s, start, end - start, end - start);
+ reflow(s, nstart, nend - nstart, nend - nstart);
+ }
+ } else {
+ transformAndReflow(s, start, end);
+ transformAndReflow(s, nstart, nend);
+ }
}
}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index bf1a596..6e45fea 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -89,3 +89,10 @@
description: "Feature flag for clearing focus when the escape key is pressed."
bug: "312921137"
}
+
+flag {
+ name: "insert_mode_crash_when_delete"
+ namespace: "text"
+ description: "A feature flag for fixing the crash while delete text in insert mode."
+ bug: "314254153"
+}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 17a3a12..7f1e037 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -34,11 +34,11 @@
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsController.LayoutInsetsDuringAnimation;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_FLOATING;
-import static android.view.InsetsState.ISIDE_LEFT;
-import static android.view.InsetsState.ISIDE_RIGHT;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_NONE;
+import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_RIGHT;
+import static android.view.InsetsSource.SIDE_TOP;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -60,7 +60,7 @@
import android.util.SparseIntArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsSource.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
@@ -142,7 +142,7 @@
if (mHasZeroInsetsIme) {
// IME has shownInsets of ZERO, and can't map to a side by default.
// Map zero insets IME to bottom, making it a special case of bottom insets.
- idSideMap.put(ID_IME, ISIDE_BOTTOM);
+ idSideMap.put(ID_IME, SIDE_BOTTOM);
}
buildSideControlsMap(idSideMap, mSideControlsMap, controls);
} else {
@@ -286,10 +286,10 @@
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
final ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_LEFT, offset.left, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_TOP, offset.top, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
@@ -499,19 +499,19 @@
final float surfaceOffset = mTranslator != null
? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
switch (side) {
- case ISIDE_LEFT:
+ case SIDE_LEFT:
m.postTranslate(-surfaceOffset, 0);
frame.offset(-offset, 0);
break;
- case ISIDE_TOP:
+ case SIDE_TOP:
m.postTranslate(0, -surfaceOffset);
frame.offset(0, -offset);
break;
- case ISIDE_RIGHT:
+ case SIDE_RIGHT:
m.postTranslate(surfaceOffset, 0);
frame.offset(offset, 0);
break;
- case ISIDE_BOTTOM:
+ case SIDE_BOTTOM:
m.postTranslate(0, surfaceOffset);
frame.offset(0, offset);
break;
@@ -543,9 +543,10 @@
// control may be null if it got revoked.
continue;
}
- @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
- if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
- side = ISIDE_BOTTOM;
+ @InternalInsetsSide int side = InsetsSource.getInsetSide(control.getInsetsHint());
+ if (side == SIDE_NONE && control.getType() == WindowInsets.Type.ime()) {
+ // IME might not provide insets when it is fullscreen or floating.
+ side = SIDE_BOTTOM;
}
sideControlsMap.add(side, control);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 0927d45..86ab213 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -46,6 +46,24 @@
*/
public class InsetsSource implements Parcelable {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SIDE_", value = {
+ SIDE_NONE,
+ SIDE_LEFT,
+ SIDE_TOP,
+ SIDE_RIGHT,
+ SIDE_BOTTOM,
+ SIDE_UNKNOWN
+ })
+ public @interface InternalInsetsSide {}
+
+ static final int SIDE_NONE = 0;
+ static final int SIDE_LEFT = 1;
+ static final int SIDE_TOP = 2;
+ static final int SIDE_RIGHT = 3;
+ static final int SIDE_BOTTOM = 4;
+ static final int SIDE_UNKNOWN = 5;
+
/** The insets source ID of IME */
public static final int ID_IME = createId(null, 0, ime());
@@ -101,6 +119,12 @@
private boolean mVisible;
+ /**
+ * Used to decide which side of the relative frame should receive insets when the frame fully
+ * covers the relative frame.
+ */
+ private @InternalInsetsSide int mSideHint = SIDE_NONE;
+
private final Rect mTmpFrame = new Rect();
public InsetsSource(int id, @InsetsType int type) {
@@ -119,6 +143,7 @@
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
+ mSideHint = other.mSideHint;
}
public void set(InsetsSource other) {
@@ -128,6 +153,7 @@
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
+ mSideHint = other.mSideHint;
}
public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -160,6 +186,18 @@
return this;
}
+ /**
+ * Updates the side hint which is used to decide which side of the relative frame should receive
+ * insets when the frame fully covers the relative frame.
+ *
+ * @param bounds A rectangle which contains the frame. It will be used to calculate the hint.
+ */
+ public InsetsSource updateSideHint(Rect bounds) {
+ mSideHint = getInsetSide(
+ calculateInsets(bounds, mFrame, true /* ignoreVisibility */));
+ return this;
+ }
+
public int getId() {
return mId;
}
@@ -236,8 +274,21 @@
return Insets.of(0, 0, 0, mTmpFrame.height());
}
- // Intersecting at top/bottom
- if (mTmpFrame.width() == relativeFrame.width()) {
+ if (mTmpFrame.equals(relativeFrame)) {
+ // Covering all sides
+ switch (mSideHint) {
+ default:
+ case SIDE_LEFT:
+ return Insets.of(mTmpFrame.width(), 0, 0, 0);
+ case SIDE_TOP:
+ return Insets.of(0, mTmpFrame.height(), 0, 0);
+ case SIDE_RIGHT:
+ return Insets.of(0, 0, mTmpFrame.width(), 0);
+ case SIDE_BOTTOM:
+ return Insets.of(0, 0, 0, mTmpFrame.height());
+ }
+ } else if (mTmpFrame.width() == relativeFrame.width()) {
+ // Intersecting at top/bottom
if (mTmpFrame.top == relativeFrame.top) {
return Insets.of(0, mTmpFrame.height(), 0, 0);
} else if (mTmpFrame.bottom == relativeFrame.bottom) {
@@ -249,9 +300,8 @@
if (mTmpFrame.top == 0) {
return Insets.of(0, mTmpFrame.height(), 0, 0);
}
- }
- // Intersecting at left/right
- else if (mTmpFrame.height() == relativeFrame.height()) {
+ } else if (mTmpFrame.height() == relativeFrame.height()) {
+ // Intersecting at left/right
if (mTmpFrame.left == relativeFrame.left) {
return Insets.of(mTmpFrame.width(), 0, 0, 0);
} else if (mTmpFrame.right == relativeFrame.right) {
@@ -283,6 +333,46 @@
}
/**
+ * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
+ * is set in order that this method returns a meaningful result.
+ */
+ static @InternalInsetsSide int getInsetSide(Insets insets) {
+ if (Insets.NONE.equals(insets)) {
+ return SIDE_NONE;
+ }
+ if (insets.left != 0) {
+ return SIDE_LEFT;
+ }
+ if (insets.top != 0) {
+ return SIDE_TOP;
+ }
+ if (insets.right != 0) {
+ return SIDE_RIGHT;
+ }
+ if (insets.bottom != 0) {
+ return SIDE_BOTTOM;
+ }
+ return SIDE_UNKNOWN;
+ }
+
+ static String sideToString(@InternalInsetsSide int side) {
+ switch (side) {
+ case SIDE_NONE:
+ return "NONE";
+ case SIDE_LEFT:
+ return "LEFT";
+ case SIDE_TOP:
+ return "TOP";
+ case SIDE_RIGHT:
+ return "RIGHT";
+ case SIDE_BOTTOM:
+ return "BOTTOM";
+ default:
+ return "UNKNOWN:" + side;
+ }
+ }
+
+ /**
* Creates an identifier of an {@link InsetsSource}.
*
* @param owner An object owned by the owner. Only the owner can modify its own sources.
@@ -331,7 +421,7 @@
}
public static String flagsToString(@Flags int flags) {
- final StringJoiner joiner = new StringJoiner(" ");
+ final StringJoiner joiner = new StringJoiner("|");
if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
joiner.add("SUPPRESS_SCRIM");
}
@@ -371,6 +461,7 @@
}
pw.print(" visible="); pw.print(mVisible);
pw.print(" flags="); pw.print(flagsToString(mFlags));
+ pw.print(" sideHint="); pw.print(sideToString(mSideHint));
pw.println();
}
@@ -393,6 +484,7 @@
if (mType != that.mType) return false;
if (mVisible != that.mVisible) return false;
if (mFlags != that.mFlags) return false;
+ if (mSideHint != that.mSideHint) return false;
if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
return mFrame.equals(that.mFrame);
@@ -400,7 +492,7 @@
@Override
public int hashCode() {
- return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
+ return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
}
public InsetsSource(Parcel in) {
@@ -414,6 +506,7 @@
}
mVisible = in.readBoolean();
mFlags = in.readInt();
+ mSideHint = in.readInt();
}
@Override
@@ -434,6 +527,7 @@
}
dest.writeBoolean(mVisible);
dest.writeInt(mFlags);
+ dest.writeInt(mSideHint);
}
@Override
@@ -442,7 +536,8 @@
+ " mType=" + WindowInsets.Type.toString(mType)
+ " mFrame=" + mFrame.toShortString()
+ " mVisible=" + mVisible
- + " mFlags=[" + flagsToString(mFlags) + "]"
+ + " mFlags=" + flagsToString(mFlags)
+ + " mSideHint=" + sideToString(mSideHint)
+ "}";
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 59e0932..c88da9e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -37,7 +37,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration.ActivityType;
@@ -48,6 +47,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSource.InternalInsetsSide;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -55,8 +55,6 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.StringJoiner;
@@ -66,23 +64,6 @@
*/
public class InsetsState implements Parcelable {
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "ISIDE", value = {
- ISIDE_LEFT,
- ISIDE_TOP,
- ISIDE_RIGHT,
- ISIDE_BOTTOM,
- ISIDE_FLOATING,
- ISIDE_UNKNOWN
- })
- public @interface InternalInsetsSide {}
- static final int ISIDE_LEFT = 0;
- static final int ISIDE_TOP = 1;
- static final int ISIDE_RIGHT = 2;
- static final int ISIDE_BOTTOM = 3;
- static final int ISIDE_FLOATING = 4;
- static final int ISIDE_UNKNOWN = 5;
-
private final SparseArray<InsetsSource> mSources;
/**
@@ -398,37 +379,14 @@
}
if (idSideMap != null) {
- @InternalInsetsSide int insetSide = getInsetSide(insets);
- if (insetSide != ISIDE_UNKNOWN) {
+ @InternalInsetsSide int insetSide = InsetsSource.getInsetSide(insets);
+ if (insetSide != InsetsSource.SIDE_UNKNOWN) {
idSideMap.put(source.getId(), insetSide);
}
}
}
/**
- * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
- * is set in order that this method returns a meaningful result.
- */
- static @InternalInsetsSide int getInsetSide(Insets insets) {
- if (Insets.NONE.equals(insets)) {
- return ISIDE_FLOATING;
- }
- if (insets.left != 0) {
- return ISIDE_LEFT;
- }
- if (insets.top != 0) {
- return ISIDE_TOP;
- }
- if (insets.right != 0) {
- return ISIDE_RIGHT;
- }
- if (insets.bottom != 0) {
- return ISIDE_BOTTOM;
- }
- return ISIDE_UNKNOWN;
- }
-
- /**
* Gets the source mapped from the ID, or creates one if no such mapping has been made.
*/
public InsetsSource getOrCreateSource(int id, int type) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c98d1d7..1b22fda 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5546,11 +5546,11 @@
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30;
+ public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60;
+ public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120;
+ public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4;
/**
* Simple constructor to use when creating a view from code.
@@ -28492,6 +28492,7 @@
surface.destroy();
}
session.kill();
+ surfaceControl.release();
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c36227..7bc832e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11991,7 +11991,7 @@
Runnable timeoutRunnable = () -> Log.e(mTag,
"Failed to submit the sync transaction after 4s. Likely to ANR "
+ "soon");
- mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+ mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER);
transaction.addTransactionCommittedListener(mSimpleExecutor,
() -> mHandler.removeCallbacks(timeoutRunnable));
surfaceSyncGroup.addTransaction(transaction);
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
index bf1d31c..fbb66d1 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
@@ -149,9 +149,12 @@
*
* Because it is not guaranteed that the events will be enqueued from a single thread, the
* implementation must be thread-safe to prevent unexpected behaviour.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
- private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+ public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
/**
* List of events held to be sent to the {@link ContentCaptureService} as a batch.
@@ -908,7 +911,7 @@
* clear the buffer events then starting sending out current event.
*/
private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
- if (forceFlush) {
+ if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) {
// The buffer events are cleared in the same thread first to prevent new events
// being added during the time of context switch. This would disrupt the sequence
// of events.
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 9f9b7b4..1dd99ba 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -8,6 +8,15 @@
}
flag {
+ name: "enable_surface_native_alloc_registration_ro"
+ namespace: "toolkit"
+ description: "Feature flag for registering surfaces with the VM for faster"
+ " cleanup. Fixed readonly version."
+ bug: "306193257"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_use_measure_cache_during_force_layout"
namespace: "toolkit"
description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d12eda3..14c5348 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,11 +1203,7 @@
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
- * @deprecated WebSQL is deprecated and this method will become a no-op on all
- * Android versions once support is removed in Chromium. See
- * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
- @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1240,11 +1236,7 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
- * @deprecated WebSQL is deprecated and this method will become a no-op on all
- * Android versions once support is removed in Chromium. See
- * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
- @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ddcfb40..57d268c 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -148,6 +148,7 @@
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.view.FloatingActionMode;
+import com.android.text.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -2343,6 +2344,13 @@
*/
void invalidateTextDisplayList(Layout layout, int start, int end) {
if (mTextRenderNodes != null && layout instanceof DynamicLayout) {
+ if (Flags.insertModeCrashWhenDelete()
+ && mTextView.isOffsetMappingAvailable()) {
+ // Text is transformed with an OffsetMapping, and we can't know the changed range
+ // on the transformed text. Invalidate the all display lists instead.
+ invalidateTextDisplayList();
+ return;
+ }
final int startTransformed =
mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER);
final int endTransformed =
diff --git a/core/java/android/widget/RemoteCanvas.java b/core/java/android/widget/RemoteCanvas.java
new file mode 100644
index 0000000..9a0898c
--- /dev/null
+++ b/core/java/android/widget/RemoteCanvas.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.widget;
+
+import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
+
+import android.annotation.AttrRes;
+import android.annotation.FlaggedApi;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.function.IntConsumer;
+
+/**
+ * {@link RemoteCanvas} is designed to support arbitrary protocols between two processes using
+ * {@link RemoteViews.DrawInstructions}. Upon instantiation in the host process,
+ * {@link RemoteCanvas#setDrawInstructions(RemoteViews.DrawInstructions)} is called so that the
+ * host process can render the {@link RemoteViews.DrawInstructions} from the provider process
+ * accordingly.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+public class RemoteCanvas extends View {
+
+ private static final String TAG = "RemoteCanvas";
+
+ @Nullable
+ private SparseArray<Runnable> mCallbacks;
+
+ private final IntConsumer mOnClickHandler = (viewId) -> {
+ if (mCallbacks == null) {
+ Log.w(TAG, "Cannot find callback for " + viewId
+ + ", in fact there were no callbacks from this RemoteViews at all.");
+ return;
+ }
+ final Runnable cb = getCallbacks().get(viewId);
+ if (cb != null) {
+ cb.run();
+ } else {
+ Log.w(TAG, "Cannot find callback for " + viewId);
+ }
+ };
+
+ RemoteCanvas(@NonNull Context context) {
+ super(context);
+ }
+
+ RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Setter method for the {@link RemoteViews.DrawInstructions} from the provider process for
+ * the host process to render accordingly.
+ *
+ * @param instructions {@link RemoteViews.DrawInstructions} from the provider process.
+ */
+ void setDrawInstructions(@NonNull final RemoteViews.DrawInstructions instructions) {
+ setTag(instructions);
+ // TODO: handle draw instructions
+ // TODO: attach mOnClickHandler
+ }
+
+ /**
+ * Adds a callback function to a clickable area in the RemoteCanvas.
+ *
+ * @param viewId the viewId of the clickable area
+ * @param cb the callback function to be triggered when clicked
+ */
+ void addOnClickHandler(final int viewId, @NonNull final Runnable cb) {
+ getCallbacks().set(viewId, cb);
+ }
+
+ /**
+ * Returns all callbacks added to the RemoteCanvas through
+ * {@link #addOnClickHandler(int, Runnable)}.
+ */
+ @VisibleForTesting
+ public SparseArray<Runnable> getCallbacks() {
+ if (mCallbacks == null) {
+ mCallbacks = new SparseArray<>();
+ }
+ return mCallbacks;
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index 6a5fc03..19a5f25 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -18,3 +18,4 @@
parcelable RemoteViews;
parcelable RemoteViews.RemoteCollectionItems;
+parcelable RemoteViews.DrawInstructions;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0d499a1..0654add 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
+import static android.appwidget.flags.Flags.drawDataParcel;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -243,6 +245,7 @@
private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
private static final int SET_REMOTE_ADAPTER_TAG = 33;
private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34;
+ private static final int SET_DRAW_INSTRUCTION_TAG = 35;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -442,6 +445,19 @@
@Nullable
private LayoutInflater.Factory2 mLayoutInflaterFactory2;
+ /**
+ * Indicates whether this {@link RemoteViews} was instantiated with a {@link DrawInstructions}
+ * object. {@link DrawInstructions} serves as an alternative protocol for the host process
+ * to render.
+ */
+ private boolean mHasDrawInstructions;
+
+ @Nullable
+ private SparseArray<PendingIntent> mPendingIntentTemplate;
+
+ @Nullable
+ private SparseArray<Intent> mFillInIntent;
+
private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
(view, pendingIntent, response) ->
startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -1463,6 +1479,11 @@
@Override
public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
+ if (hasDrawInstructions() && root instanceof RemoteCanvas target) {
+ target.addOnClickHandler(mViewId, () ->
+ mResponse.handleViewInteraction(root, params.handler));
+ return;
+ }
final View target = root.findViewById(mViewId);
if (target == null) return;
@@ -3851,6 +3872,45 @@
}
}
+ private static class SetDrawInstructionAction extends Action {
+
+ @Nullable
+ private final DrawInstructions mInstructions;
+
+ SetDrawInstructionAction(@NonNull final DrawInstructions instructions) {
+ mInstructions = instructions;
+ }
+
+ SetDrawInstructionAction(@NonNull final Parcel in) {
+ if (drawDataParcel()) {
+ mInstructions = DrawInstructions.readFromParcel(in);
+ } else {
+ mInstructions = null;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (drawDataParcel()) {
+ DrawInstructions.writeToParcel(mInstructions, dest, flags);
+ }
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+ throws ActionException {
+ if (drawDataParcel() && mInstructions != null
+ && root instanceof RemoteCanvas remoteCanvas) {
+ remoteCanvas.setDrawInstructions(mInstructions);
+ }
+ }
+
+ @Override
+ public int getActionTag() {
+ return SET_DRAW_INSTRUCTION_TAG;
+ }
+ }
+
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
@@ -4080,6 +4140,7 @@
mClassCookies = src.mClassCookies;
mIdealSize = src.mIdealSize;
mProviderInstanceId = src.mProviderInstanceId;
+ mHasDrawInstructions = src.mHasDrawInstructions;
if (src.hasLandscapeAndPortraitLayouts()) {
mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot);
@@ -4114,12 +4175,26 @@
/**
* Reads a RemoteViews object from a parcel.
*
- * @param parcel
+ * @param parcel the parcel object
*/
public RemoteViews(Parcel parcel) {
this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
}
+ /**
+ * Instantiates a RemoteViews object using {@link DrawInstructions}, which serves as an
+ * alternative to XML layout. {@link DrawInstructions} objects contains the instructions which
+ * can be interpreted and rendered accordingly in the host process.
+ *
+ * @param drawInstructions The {@link DrawInstructions} object
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public RemoteViews(@NonNull final DrawInstructions drawInstructions) {
+ Objects.requireNonNull(drawInstructions);
+ mHasDrawInstructions = true;
+ addAction(new SetDrawInstructionAction(drawInstructions));
+ }
+
private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@Nullable ApplicationInfo info, int depth) {
if (depth > MAX_NESTED_VIEWS
@@ -4178,6 +4253,7 @@
}
mApplyFlags = parcel.readInt();
mProviderInstanceId = parcel.readLong();
+ mHasDrawInstructions = parcel.readBoolean();
// Ensure that all descendants have their caches set up recursively.
if (mIsRoot) {
@@ -4254,6 +4330,8 @@
return new AttributeReflectionAction(parcel);
case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG:
return new SetOnStylusHandwritingResponse(parcel);
+ case SET_DRAW_INSTRUCTION_TAG:
+ return new SetDrawInstructionAction(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -4747,7 +4825,12 @@
* by a child of viewId and executed when that child is clicked
*/
public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) {
- addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+ if (hasDrawInstructions()) {
+ getPendingIntentTemplate().set(viewId, pendingIntentTemplate);
+ tryAddRemoteResponse(viewId);
+ } else {
+ addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+ }
}
/**
@@ -4768,7 +4851,12 @@
* in order to determine the on-click behavior of the view specified by viewId
*/
public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) {
- setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
+ if (hasDrawInstructions()) {
+ getFillInIntent().set(viewId, fillInIntent);
+ tryAddRemoteResponse(viewId);
+ } else {
+ setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
+ }
}
/**
@@ -5791,6 +5879,10 @@
}
}
+ private boolean hasDrawInstructions() {
+ return mHasDrawInstructions;
+ }
+
private RemoteViews getRemoteViewsToApply(Context context) {
if (hasLandscapeAndPortraitLayouts()) {
int orientation = context.getResources().getConfiguration().orientation;
@@ -5973,6 +6065,10 @@
if (applyThemeResId != 0) {
inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId);
}
+ // If the RemoteViews contains draw instructions, just use it instead.
+ if (rv.hasDrawInstructions()) {
+ return new RemoteCanvas(inflationContext);
+ }
LayoutInflater inflater = LayoutInflater.from(context);
// Clone inflater so we load resources from correct context and
@@ -6236,7 +6332,7 @@
/** @hide */
public boolean canRecycleView(@Nullable View v) {
- if (v == null) {
+ if (v == null || hasDrawInstructions()) {
return false;
}
Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame);
@@ -6388,6 +6484,32 @@
return context;
}
+ @NonNull
+ private SparseArray<PendingIntent> getPendingIntentTemplate() {
+ if (mPendingIntentTemplate == null) {
+ mPendingIntentTemplate = new SparseArray<>();
+ }
+ return mPendingIntentTemplate;
+ }
+
+ @NonNull
+ private SparseArray<Intent> getFillInIntent() {
+ if (mFillInIntent == null) {
+ mFillInIntent = new SparseArray<>();
+ }
+ return mFillInIntent;
+ }
+
+ private void tryAddRemoteResponse(final int viewId) {
+ final PendingIntent pendingIntent = getPendingIntentTemplate().get(viewId);
+ final Intent intent = getFillInIntent().get(viewId);
+ if (pendingIntent != null && intent != null) {
+ addAction(new SetOnClickResponse(viewId,
+ RemoteResponse.fromPendingIntentTemplateAndFillInIntent(
+ pendingIntent, intent)));
+ }
+ }
+
/**
* Utility class to hold all the options when applying the remote views
* @hide
@@ -6624,6 +6746,7 @@
}
dest.writeInt(mApplyFlags);
dest.writeLong(mProviderInstanceId);
+ dest.writeBoolean(mHasDrawInstructions);
dest.restoreAllowSquashing(prevSquashingAllowed);
}
@@ -6926,6 +7049,14 @@
return response;
}
+ private static RemoteResponse fromPendingIntentTemplateAndFillInIntent(
+ @NonNull final PendingIntent pendingIntent, @NonNull final Intent intent) {
+ RemoteResponse response = new RemoteResponse();
+ response.mPendingIntent = pendingIntent;
+ response.mFillIntent = intent;
+ return response;
+ }
+
/**
* Adds a shared element to be transferred as part of the transition between Activities
* using cross-Activity scene animations. The position of the first element will be used as
@@ -6964,8 +7095,8 @@
private void writeToParcel(Parcel dest, int flags) {
PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
- if (mPendingIntent == null) {
- // Only write the intent if pending intent is null
+ dest.writeBoolean((mFillIntent != null));
+ if (mFillIntent != null) {
dest.writeTypedObject(mFillIntent, flags);
}
dest.writeInt(mInteractionType);
@@ -6975,9 +7106,7 @@
private void readFromParcel(Parcel parcel) {
mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
- if (mPendingIntent == null) {
- mFillIntent = parcel.readTypedObject(Intent.CREATOR);
- }
+ mFillIntent = parcel.readBoolean() ? parcel.readTypedObject(Intent.CREATOR) : null;
mInteractionType = parcel.readInt();
int[] viewIds = parcel.createIntArray();
mViewIds = viewIds == null ? null : IntArray.wrap(viewIds);
@@ -7054,7 +7183,7 @@
/** @hide */
public Pair<Intent, ActivityOptions> getLaunchOptions(View view) {
- Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent);
+ Intent intent = mFillIntent == null ? new Intent() : new Intent(mFillIntent);
intent.setSourceBounds(getSourceBounds(view));
if (view instanceof CompoundButton
@@ -7413,6 +7542,98 @@
}
/**
+ * A data parcel that carries the instructions to draw the RemoteViews, as an alternative to
+ * XML layout.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public static final class DrawInstructions {
+
+ @NonNull
+ private final List<byte[]> mInstructions;
+
+ private DrawInstructions() {
+ throw new UnsupportedOperationException(
+ "DrawInstructions cannot be instantiate without instructions");
+ }
+
+ private DrawInstructions(@NonNull List<byte[]> instructions) {
+ // Create and retain an immutable copy of given instructions.
+ mInstructions = new ArrayList<>(instructions.size());
+ for (byte[] instruction : instructions) {
+ final int len = instruction.length;
+ final byte[] target = new byte[len];
+ System.arraycopy(instruction, 0, target, 0, len);
+ mInstructions.add(target);
+ }
+ }
+
+ @Nullable
+ private static DrawInstructions readFromParcel(@NonNull final Parcel in) {
+ int size = in.readInt();
+ if (size == -1) {
+ return null;
+ }
+ byte[] instruction;
+ final List<byte[]> instructions = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ instruction = new byte[in.readInt()];
+ in.readByteArray(instruction);
+ instructions.add(instruction);
+ }
+ return new DrawInstructions(instructions);
+ }
+ private static void writeToParcel(@Nullable final DrawInstructions drawInstructions,
+ @NonNull final Parcel dest, final int flags) {
+ if (drawInstructions == null) {
+ dest.writeInt(-1);
+ return;
+ }
+ final List<byte[]> instructions = drawInstructions.mInstructions;
+ dest.writeInt(instructions.size());
+ for (byte[] instruction : instructions) {
+ dest.writeInt(instruction.length);
+ dest.writeByteArray(instruction);
+ }
+ }
+
+ /**
+ * Append additional instructions to this {@link DrawInstructions} object.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public void appendInstructions(@NonNull final byte[] instructions) {
+ mInstructions.add(instructions);
+ }
+
+ /**
+ * Builder class for {@link DrawInstructions} objects.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public static final class Builder {
+
+ private final List<byte[]> mInstructions;
+
+ /**
+ * Constructor.
+ *
+ * @param instructions Information to draw the RemoteViews.
+ */
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public Builder(@NonNull final List<byte[]> instructions) {
+ mInstructions = new ArrayList<>(instructions);
+ }
+
+ /**
+ * Creates a {@link DrawInstructions} instance.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ public DrawInstructions build() {
+ return new DrawInstructions(mInstructions);
+ }
+ }
+ }
+
+ /**
* Get the ID of the top-level view of the XML layout, if set using
* {@link RemoteViews#RemoteViews(String, int, int)}.
*/
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 5dbf328..93297e6 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -94,11 +94,18 @@
@Nullable
private final IBinder mPairedActivityToken;
+ /**
+ * If {@code true}, transitions are allowed even if the TaskFragment is empty. If
+ * {@code false}, transitions will wait until the TaskFragment becomes non-empty or other
+ * conditions are met. Default to {@code false}.
+ */
+ private final boolean mAllowTransitionWhenEmpty;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
- @Nullable IBinder pairedActivityToken) {
+ @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -110,6 +117,7 @@
mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
}
@NonNull
@@ -155,6 +163,11 @@
return mPairedActivityToken;
}
+ /** @hide */
+ public boolean getAllowTransitionWhenEmpty() {
+ return mAllowTransitionWhenEmpty;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -163,6 +176,7 @@
mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
+ mAllowTransitionWhenEmpty = in.readBoolean();
}
/** @hide */
@@ -175,6 +189,7 @@
dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
+ dest.writeBoolean(mAllowTransitionWhenEmpty);
}
@NonNull
@@ -201,6 +216,7 @@
+ " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ "}";
}
@@ -234,6 +250,8 @@
@Nullable
private IBinder mPairedActivityToken;
+ private boolean mAllowTransitionWhenEmpty;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -298,12 +316,26 @@
return this;
}
+ /**
+ * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+ * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+ * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+ * to {@code false}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken);
+ mPairedActivityToken, mAllowTransitionWhenEmpty);
}
}
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cbf6367..edfbea4 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -50,3 +50,11 @@
description: "Whether we should allow hiding the size compat restart button"
bug: "318840081"
}
+
+flag {
+ name: "configurable_font_scale_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the font_scale is read from a device dependent configuration file"
+ bug: "319808237"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index eeea17b..90ca95a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -71,7 +71,7 @@
"persist.debug.sysui.notification.notif_cooldown_t1", 60000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
+ "persist.debug.sysui.notification.notif_cooldown_t2", 10000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_VOLUME1 = devFlag(
"persist.debug.sysui.notification.notif_volume1", 30);
@@ -81,6 +81,10 @@
public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
"persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
+ /** Value used by polite notif. feature */
+ public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag(
+ "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000);
+
/** b/303716154: For debugging only: use short bitmap duration. */
public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
"persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e58f4f0..88aa89a 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class MetricsLogger {
// define metric categories in frameworks/base/proto/src/metrics_constants.proto.
// mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
index 6786427..df8bf31 100644
--- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
+++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
@@ -12,6 +12,7 @@
*
* @hide.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class FakeMetricsLogger extends MetricsLogger {
private Queue<LogMaker> logs = new LinkedList<>();
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index e303890..6787ddc 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -27,6 +27,7 @@
*
* @hide.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class UiEventLoggerFake implements UiEventLogger {
/**
* Immutable data class used to record fake log events.
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 5d82d04..12aff1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
import android.os.Build;
import android.os.PatternMatcher;
import android.util.Slog;
@@ -126,6 +127,10 @@
.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestProvider_singleUser, sa));
+ if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+ provider.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SYSTEM_USER_ONLY,
+ R.styleable.AndroidManifestProvider_systemUserOnly, sa));
+ }
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index a1dd19a3..4ac542f8 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
import android.os.Build;
import com.android.internal.R;
@@ -105,6 +106,11 @@
| flag(ServiceInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestService_singleUser, sa)));
+ if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+ service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY,
+ R.styleable.AndroidManifestService_systemUserOnly, sa));
+ }
+
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestService_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
new file mode 100644
index 0000000..25e3003
--- /dev/null
+++ b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Callback interface between LockSettingService and other system services to be notified about the
+ * state of primary authentication (i.e. PIN/pattern/password).
+ * @hide
+ */
+oneway interface ILockSettingsStateListener {
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationSucceeded(int userId);
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationFailed(int userId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 0704cb8..ae8f025 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,9 +18,11 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Build;
import android.os.Trace;
import android.text.BoringLayout;
import android.text.Layout;
+import android.text.PrecomputedText;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.method.TransformationMethod;
@@ -48,6 +50,10 @@
private int mLayoutMaxLines = -1;
private int mImageEndMargin;
+ private int mStaticLayoutCreationCountInOnMeasure = 0;
+
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
public ImageFloatingTextView(Context context) {
this(context, null);
}
@@ -71,7 +77,10 @@
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize,
TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
- Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+ mStaticLayoutCreationCountInOnMeasure++;
+ }
TransformationMethod transformationMethod = getTransformationMethod();
CharSequence text = getText();
if (transformationMethod != null) {
@@ -79,7 +88,7 @@
}
text = text == null ? "" : text;
StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
- getPaint(), wantWidth)
+ getPaint(), wantWidth)
.setAlignment(alignment)
.setTextDirection(getTextDirectionHeuristic())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
@@ -115,7 +124,10 @@
}
final StaticLayout result = builder.build();
- Trace.endSection();
+ if (TRACE_ONMEASURE) {
+ trackMaxLines();
+ Trace.endSection();
+ }
return result;
}
@@ -141,7 +153,10 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Trace.beginSection("ImageFloatingTextView#onMeasure");
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("ImageFloatingTextView#onMeasure");
+ }
+ mStaticLayoutCreationCountInOnMeasure = 0;
int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
if (getLayout() != null && getLayout().getHeight() != availableHeight) {
// We've been measured before and the new size is different than before, lets make sure
@@ -168,7 +183,12 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
- Trace.endSection();
+
+
+ if (TRACE_ONMEASURE) {
+ trackParameters();
+ Trace.endSection();
+ }
}
@Override
@@ -216,4 +236,37 @@
requestLayout();
}
}
+
+ private void trackParameters() {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+ Trace.setCounter("ImageFloatingView#staticLayoutCreationCount",
+ mStaticLayoutCreationCountInOnMeasure);
+ Trace.setCounter("ImageFloatingView#isPrecomputedText",
+ isTextAPrecomputedText());
+ }
+ /**
+ * @return 1 if {@link TextView#getText()} is PrecomputedText, else 0
+ */
+ private int isTextAPrecomputedText() {
+ final CharSequence text = getText();
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+
+ if (text instanceof PrecomputedText) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private void trackMaxLines() {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+
+ Trace.setCounter("ImageFloatingView#layoutMaxLines", mLayoutMaxLines);
+ }
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 757978b..b5b3a48 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -333,11 +333,17 @@
@UnsupportedAppUsage
public LockPatternUtils(Context context) {
+ this(context, null);
+ }
+
+ @VisibleForTesting
+ public LockPatternUtils(Context context, ILockSettings lockSettings) {
mContext = context;
mContentResolver = context.getContentResolver();
Looper looper = Looper.myLooper();
mHandler = looper != null ? new Handler(looper) : null;
+ mLockSettingsService = lockSettings;
}
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 8114e1f..627e877 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -166,4 +166,16 @@
* Refreshes pending strong auth timeout with the latest admin requirement set by device policy.
*/
public abstract void refreshStrongAuthTimeout(int userId);
+
+ /**
+ * Register a LockSettingsStateListener
+ * @param listener The listener to be registered
+ */
+ public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener);
+
+ /**
+ * Unregister a LockSettingsStateListener
+ * @param listener The listener to be unregistered
+ */
+ public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener);
}
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index c06f5f7..e07acac 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -45,6 +47,8 @@
private int mMaxDisplayedLines = Integer.MAX_VALUE;
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -67,6 +71,10 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("MessagingLinearLayout#onMeasure");
+ trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
+ }
// This is essentially a bottom-up linear layout that only adds children that fit entirely
// up to a maximum height.
int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -177,6 +185,9 @@
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
Math.max(getSuggestedMinimumHeight(), totalHeight));
+ if (TRACE_ONMEASURE) {
+ Trace.endSection();
+ }
}
@Override
@@ -240,6 +251,25 @@
}
}
+ private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+
+ final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize",
+ availableWidth);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode",
+ widthMode);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize",
+ availableHeight);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode",
+ heightMode);
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7af69f2..6a640a5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,6 +28,7 @@
#include <meminfo/sysmeminfo.h>
#include <processgroup/processgroup.h>
#include <processgroup/sched_policy.h>
+#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <algorithm>
@@ -232,6 +233,31 @@
}
}
+// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in
+// /proc/${pid}/status since at least kernel v2.5.
+static int uid_from_pid(int pid)
+{
+ int uid = -1;
+ std::array<char, 64> path;
+ int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid);
+ if (res < 0 || res >= static_cast<int>(path.size())) {
+ DCHECK(false);
+ return uid;
+ }
+ FILE* f = fopen(path.data(), "r");
+ if (!f) {
+ return uid;
+ }
+ char line[256];
+ while (fgets(line, sizeof(line), f)) {
+ if (sscanf(line, "Uid: %d", &uid) == 1) {
+ break;
+ }
+ }
+ fclose(f);
+ return uid;
+}
+
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -275,7 +301,12 @@
}
}
- if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+ const int uid = uid_from_pid(pid);
+ if (uid < 0) {
+ signalExceptionForGroupError(env, ESRCH, pid);
+ return;
+ }
+ if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 25b2aaf..98f409a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -343,7 +343,9 @@
const std::vector<SurfaceControlStats>& /*stats*/) {
JNIEnv* env = getenv();
// Adding a strong reference for java SyncFence
- presentFence->incStrong(0);
+ if (presentFence) {
+ presentFence->incStrong(0);
+ }
jobject stats =
env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f9731d4..070e2cb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3775,6 +3775,13 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to access EnhancedConfirmationManager.
+ @SystemApi
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")
+ @hide This is not a third-party API (intended for OEMs and system apps). -->
+ <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
+ android:protectionLevel="signature|installer" />
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup|knownSigner"
@@ -7058,6 +7065,7 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to access the smartspace service as a client.
+ @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
@@ -7954,11 +7962,11 @@
<!-- @SystemApi Allows an application to read the system grammatical gender.
@FlaggedApi("android.app.system_terms_of_address_enabled")
- <p>Protection level: signature|privileged|appop
+ <p>Protection level: signature|privileged
@hide
-->
<permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
- android:protectionLevel="signature|privileged|appop"/>
+ android:protectionLevel="signature|privileged"/>
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 29086a45..35276bf 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -506,6 +506,12 @@
receivers, and providers; it can not be used with activities. -->
<attr name="singleUser" format="boolean" />
+ <!-- If set to true, only a single instance of this component will
+ run and be available for the SYSTEM user. Non SYSTEM users will not be
+ allowed to access the component if this flag is enabled.
+ This flag can be used with services, receivers, providers and activities. -->
+ <attr name="systemUserOnly" format="boolean" />
+
<!-- Specify a specific process that the associated code is to run in.
Use with the application tag (to supply a default process for all
application components), or with the activity, receiver, service,
@@ -2865,6 +2871,7 @@
Context.createAttributionContext() using the first attribution tag
contained here. -->
<attr name="attributionTags" />
+ <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3023,6 +3030,7 @@
ignored when the process is bound into a shared isolated process by a client.
-->
<attr name="allowSharedIsolatedProcess" format="boolean" />
+ <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3150,7 +3158,7 @@
<attr name="uiOptions" />
<attr name="parentActivityName" />
<attr name="singleUser" />
- <!-- @hide This broadcast receiver or activity will only receive broadcasts for the
+ <!-- This broadcast receiver or activity will only receive broadcasts for the
system user-->
<attr name="systemUserOnly" format="boolean" />
<attr name="persistableMode" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5e2aacd..0d1a987 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6905,4 +6905,7 @@
<!-- Whether to show a percentage text next to the progressbar while preparing to update the
device -->
<bool name="config_showPercentageTextDuringRebootToUpdate">true</bool>
+
+ <!-- Defines the minimum interval (in ms) between two input-based user-activity poke events. -->
+ <integer name="config_minMillisBetweenInputUserActivityEvents">100</integer>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 7d22885..b8fc052 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -121,6 +121,8 @@
<public name="adServiceTypes" />
<!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
<public name="featureFlag"/>
+ <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
+ <public name="systemUserOnly"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 33ea02a..3c1a42f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5320,4 +5320,6 @@
<!-- Shutdown thread config flags -->
<java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
+
+ <java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d1a90ae..4406302 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -212,8 +212,8 @@
"src/android/database/CursorWindowTest.java",
"src/android/os/**/*.java",
"src/android/util/**/*.java",
+ "src/com/android/internal/logging/**/*.java",
"src/com/android/internal/os/**/*.java",
- "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2d3e123..2a718ff 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -71,7 +70,6 @@
*/
@Test
@SmallTest
- @IgnoreUnderRavenwood(blockedBy = Build.class)
public void testBuildFields() throws Exception {
assertNotEmpty("ID", Build.ID);
assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 593833ec..b2c005f 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -34,7 +34,6 @@
* while tracing on the emulator and then run traceview to view the trace.
*/
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Trace.class)
public class TraceTest {
private static final String TAG = "TraceTest";
@@ -46,7 +45,51 @@
private int gMethodCalls = 0;
@Test
+ public void testEnableDisable() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.setTracingEnabled(true, 0);
+ Trace.setTracingEnabled(false, 0);
+
+ Trace.setAppTracingAllowed(true);
+ Trace.setAppTracingAllowed(false);
+ }
+
+ @Test
+ public void testBeginEnd() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42);
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+ Trace.beginSection(TAG);
+ Trace.endSection();
+
+ Trace.beginAsyncSection(TAG, 42);
+ Trace.endAsyncSection(TAG, 42);
+ }
+
+ @Test
+ public void testCounter() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+ Trace.setCounter(TAG, 42);
+ }
+
+ @Test
+ public void testInstant() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG);
+ }
+
+ @Test
public void testNullStrings() {
+ // Currently only verifying that we can invoke without crashing
Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
@@ -62,6 +105,7 @@
@Test
@SmallTest
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void testNativeTracingFromJava()
{
long start = System.currentTimeMillis();
@@ -82,6 +126,7 @@
// This should not run in the automated suite.
@Suppress
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void disableTestNativeTracingFromC()
{
long start = System.currentTimeMillis();
@@ -97,6 +142,7 @@
@Test
@LargeTest
@Suppress // Failing.
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void testMethodTracing()
{
long start = System.currentTimeMillis();
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 906d84e..672875a 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -20,8 +20,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_TOP;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -106,8 +106,8 @@
typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
- assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
- assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
+ assertEquals(SIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+ assertEquals(SIDE_BOTTOM, typeSideMap.get(ID_IME));
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
index f0f3a96..0075128 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
@@ -433,6 +433,72 @@
assertThat(session.mEvents).isEmpty();
}
+ @Test
+ public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE - 1; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(0))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isNull();
+ assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1);
+ }
+
+ @Test
+ public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isEmpty();
+ assertThat(session.mEventProcessQueue).isEmpty();
+ }
+
+ @Test
+ public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(2))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isEmpty();
+ assertThat(session.mEventProcessQueue).hasSize(1);
+ }
+
/** Simulates the regular content capture events sequence. */
private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) {
final ArrayList<Object> events = new ArrayList<>(
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 15c9047..543d73b 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.appwidget.flags.Flags.drawDataParcel;
+
import static com.android.internal.R.id.pending_intent_tag;
import static org.junit.Assert.assertArrayEquals;
@@ -63,6 +65,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -414,6 +417,48 @@
assertNotNull(view.findViewById(R.id.light_background_text));
}
+ @Test
+ public void remoteCanvasCanAccessDrawInstructions() {
+ if (!drawDataParcel()) {
+ return;
+ }
+ final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+ final RemoteViews rv = new RemoteViews(drawInstructions);
+ final View view = rv.apply(mContext, mContainer);
+ assertTrue(view instanceof RemoteCanvas);
+ assertEquals(drawInstructions, view.getTag());
+ }
+
+ @Test
+ public void remoteCanvasWiresClickHandlers() {
+ if (!drawDataParcel()) {
+ return;
+ }
+ final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+ final RemoteViews rv = new RemoteViews(drawInstructions);
+ final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
+ final Intent i = new Intent().putExtra("TEST", "Success");
+ final int viewId = 1;
+ rv.setPendingIntentTemplate(viewId, pi);
+ rv.setOnClickFillInIntent(viewId, i);
+ final View view = rv.apply(mContext, mContainer);
+ assertTrue(view instanceof RemoteCanvas);
+ RemoteCanvas target = (RemoteCanvas) view;
+ assertEquals(1, target.getCallbacks().size());
+ assertNotNull(target.getCallbacks().get(viewId));
+ }
+
+ private RemoteViews.DrawInstructions getDrawInstructions() {
+ final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
+ final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
+ final RemoteViews.DrawInstructions drawInstructions =
+ new RemoteViews.DrawInstructions.Builder(
+ Collections.singletonList(first)).build();
+ drawInstructions.appendInstructions(second);
+ return drawInstructions;
+ }
+
private RemoteViews createViewChained(int depth, String... texts) {
RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
new file mode 100644
index 0000000..7054cc0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MetricsLoggerTest {
+ private FakeMetricsLogger mLogger;
+
+ private static final int TEST_ACTION = 42;
+
+ @Before
+ public void setUp() throws Exception {
+ mLogger = new FakeMetricsLogger();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(mLogger.getLogs().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void testAction() throws Exception {
+ mLogger.action(TEST_ACTION);
+ assertThat(mLogger.getLogs().size()).isEqualTo(1);
+ final LogMaker event = mLogger.getLogs().peek();
+ assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION);
+ assertThat(event.getCategory()).isEqualTo(TEST_ACTION);
+ }
+
+ @Test
+ public void testVisible() throws Exception {
+ // Limited testing to confirm we don't crash
+ mLogger.visible(TEST_ACTION);
+ mLogger.hidden(TEST_ACTION);
+ mLogger.visibility(TEST_ACTION, true);
+ mLogger.visibility(TEST_ACTION, false);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
new file mode 100644
index 0000000..7840f71
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.testing.UiEventLoggerFake;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UiEventLoggerTest {
+ private UiEventLoggerFake mLogger;
+
+ private static final int TEST_EVENT_ID = 42;
+ private static final int TEST_INSTANCE_ID = 21;
+
+ private enum MyUiEventEnum implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Example event")
+ TEST_EVENT(TEST_EVENT_ID);
+
+ private final int mId;
+
+ MyUiEventEnum(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID);
+
+ @Before
+ public void setUp() throws Exception {
+ mLogger = new UiEventLoggerFake();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(mLogger.numLogs()).isEqualTo(0);
+ }
+
+ @Test
+ public void testSimple() throws Exception {
+ mLogger.log(MyUiEventEnum.TEST_EVENT);
+ assertThat(mLogger.numLogs()).isEqualTo(1);
+ assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+ }
+
+ @Test
+ public void testWithInstance() throws Exception {
+ mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE);
+ assertThat(mLogger.numLogs()).isEqualTo(1);
+ assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+ assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index ec4c563..06d888b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,10 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,9 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MonotonicClockTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private final MockClock mClock = new MockClock();
private File mFile;
@@ -70,6 +77,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "b/321832617")
public void corruptedFile() throws IOException {
// Create an invalid binary XML file to cause IOException: "Unexpected magic number"
try (FileWriter w = new FileWriter(mFile)) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 1a668f7..b90480a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -16,20 +16,279 @@
package com.android.internal.widget;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.trust.TrustManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
public class LockPatternUtilsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ private ILockSettings mLockSettings;
+ private static final int USER_ID = 1;
+ private static final int DEMO_USER_ID = 5;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
+ throws Exception {
+ mLockSettings = mock(ILockSettings.class);
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ final MockContentResolver cr = new MockContentResolver(context);
+ cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(context.getContentResolver()).thenReturn(cr);
+ Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
+
+ when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
+ isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ : LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+ DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
+ when(mLockSettings.hasSecureLockScreen()).thenReturn(true);
+ mLockPatternUtils = new LockPatternUtils(context, mLockSettings);
+
+ final UserInfo userInfo = mock(UserInfo.class);
+ when(userInfo.isDemo()).thenReturn(isDemoUser);
+ final UserManager um = mock(UserManager.class);
+ when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
+ when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
+ }
+
+ @Test
+ public void isUserInLockDown() throws Exception {
+ configureTest(true, false, 2);
+
+ // GIVEN strong auth not required
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+ // THEN user isn't in lockdown
+ assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown and lockout
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isDemoUser_true() throws Exception {
+ configureTest(false, true, 2);
+ assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
+ configureTest(true, true, 2);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
+ configureTest(false, false, 2);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
+ configureTest(false, true, 0);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void testAddWeakEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ int testUserId = 10;
+ IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+ mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+ verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+ }
+
+ @Test
+ public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testRemoveAutoEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+ verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenActive() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+ verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenValid() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+ verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+ }
+
+ @Test
+ public void testSetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void testSetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> knownTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+ assertTrue(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ assertFalse(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
@Test
public void testUserFrp_isNotRegularUser() throws Exception {
@@ -56,4 +315,49 @@
assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
}
+
+ private TestStrongAuthTracker createStrongAuthTracker() {
+ final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+ return new TestStrongAuthTracker(context, Looper.getMainLooper());
+ }
+
+ private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+ TestStrongAuthTracker(Context context, Looper looper) {
+ super(context, looper);
+ }
+
+ public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+ handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+ }
+ }
+
+ private ILockSettings createTestLockSettings() {
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ final TrustManager trustManager = mock(TrustManager.class);
+ when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager);
+
+ final ILockSettings ils = mock(ILockSettings.class);
+ mLockPatternUtils = new LockPatternUtils(context, ils);
+ return ils;
+ }
+
+ private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+ return new IWeakEscrowTokenActivatedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenActivated(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
+
+ private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+ return new IWeakEscrowTokenRemovedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenRemoved(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
}
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e..21aa3c44 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@
static_libs: [
"android-common",
"frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
],
libs: [
"android.test.runner",
@@ -23,3 +26,22 @@
platform_apis: true,
certificate: "platform",
}
+
+android_ravenwood_test {
+ name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+ static_libs: [
+ "android-common",
+ "frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bf..ea65de0 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
package android.os;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
import android.test.suitebuilder.annotation.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class SystemPropertiesTest extends TestCase {
+public class SystemPropertiesTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setSystemPropertyMutable(KEY, null)
+ .setSystemPropertyMutable(UNSET_KEY, null)
+ .setSystemPropertyMutable(PERSIST_KEY, null)
+ .build();
+
private static final String KEY = "sys.testkey";
private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
private static final String PERSIST_KEY = "persist.sys.testkey";
+ @Test
@SmallTest
public void testStressPersistPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@
}
}
+ @Test
@SmallTest
public void testStressMemoryPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@
}
}
+ @Test
@SmallTest
public void testProperties() throws Exception {
String value;
@@ -93,6 +112,7 @@
assertEquals(expected, value);
}
+ @Test
@SmallTest
public void testHandle() throws Exception {
String value;
@@ -114,6 +134,7 @@
assertEquals(12345, handle.getInt(12345));
}
+ @Test
@SmallTest
public void testIntegralProperties() throws Exception {
testInt("", 123, 123);
@@ -133,6 +154,7 @@
testLong("-3147483647", 124, -3147483647L);
}
+ @Test
@SmallTest
public void testUnset() throws Exception {
assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@
assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
}
+ @Test
@SmallTest
@SuppressWarnings("null")
public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@
}
}
+ @Test
@SmallTest
public void testCallbacks() {
// Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@
}
}
+ @Test
@SmallTest
public void testDigestOf() {
final String empty = SystemProperties.digestOf();
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
deleted file mode 100644
index dcaf676..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ /dev/null
@@ -1,339 +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.internal.util;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
-import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
-import com.android.internal.widget.LockPatternUtils;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
-public class LockPatternUtilsTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- private ILockSettings mLockSettings;
- private static final int USER_ID = 1;
- private static final int DEMO_USER_ID = 5;
-
- private LockPatternUtils mLockPatternUtils;
-
- private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
- throws Exception {
- mLockSettings = Mockito.mock(ILockSettings.class);
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-
- final MockContentResolver cr = new MockContentResolver(context);
- cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(context.getContentResolver()).thenReturn(cr);
- Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
-
- when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
- isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
- : LockPatternUtils.CREDENTIAL_TYPE_NONE);
- when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
- DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
- // TODO(b/63758238): stop spying the class under test
- mLockPatternUtils = spy(new LockPatternUtils(context));
- when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
- doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
-
- final UserInfo userInfo = Mockito.mock(UserInfo.class);
- when(userInfo.isDemo()).thenReturn(isDemoUser);
- final UserManager um = Mockito.mock(UserManager.class);
- when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
- when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
- }
-
- @Test
- public void isUserInLockDown() throws Exception {
- configureTest(true, false, 2);
-
- // GIVEN strong auth not required
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
-
- // THEN user isn't in lockdown
- assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
-
- // GIVEN lockdown
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-
- // THEN user is in lockdown
- assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-
- // GIVEN lockdown and lockout
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- // THEN user is in lockdown
- assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isDemoUser_true() throws Exception {
- configureTest(false, true, 2);
- assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
- configureTest(true, true, 2);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
- configureTest(false, false, 2);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
- configureTest(false, true, 0);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void testAddWeakEscrowToken() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
- int testUserId = 10;
- IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
- mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
- verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
- }
-
- @Test
- public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
- mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
- verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
- }
-
- @Test
- public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
- mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
- verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
- }
-
- @Test
- public void testRemoveAutoEscrowToken() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- long testHandle = 100L;
- mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
- verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
- }
-
- @Test
- public void testIsAutoEscrowTokenActive() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- long testHandle = 100L;
- mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
- verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
- }
-
- @Test
- public void testIsAutoEscrowTokenValid() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
- long testHandle = 100L;
- mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
- verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
- }
-
- @Test
- public void testSetEnabledTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
- doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
- List<ComponentName> enabledTrustAgents = Lists.newArrayList(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
-
- mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
-
- assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
- }
-
- @Test
- public void testGetEnabledTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn(
- "com.android/.TrustAgent,com.test/.TestAgent");
-
- List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-
- assertThat(trustAgents).containsExactly(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
- }
-
- @Test
- public void testSetKnownTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
- doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
- List<ComponentName> knownTrustAgents = Lists.newArrayList(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
-
- mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
-
- assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
- }
-
- @Test
- public void testGetKnownTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn(
- "com.android/.TrustAgent,com.test/.TestAgent");
-
- List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
-
- assertThat(trustAgents).containsExactly(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
- }
-
- @Test
- public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
- throws RemoteException {
- TestStrongAuthTracker tracker = createStrongAuthTracker();
- tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
-
- assertTrue(tracker.isBiometricAllowedForUser(
- /* isStrongBiometric = */ true,
- DEMO_USER_ID));
- }
-
- @Test
- public void isBiometricAllowedForUser_afterLockout_returnsFalse()
- throws RemoteException {
- TestStrongAuthTracker tracker = createStrongAuthTracker();
- tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- assertFalse(tracker.isBiometricAllowedForUser(
- /* isStrongBiometric = */ true,
- DEMO_USER_ID));
- }
-
-
- private TestStrongAuthTracker createStrongAuthTracker() {
- final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
- return new TestStrongAuthTracker(context, Looper.getMainLooper());
- }
-
- private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-
- TestStrongAuthTracker(Context context, Looper looper) {
- super(context, looper);
- }
-
- public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
- handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
- }
- }
-
- private ILockSettings createTestLockSettings() {
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mLockPatternUtils = spy(new LockPatternUtils(context));
- final ILockSettings ils = Mockito.mock(ILockSettings.class);
- when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
- return ils;
- }
-
- private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
- return new IWeakEscrowTokenActivatedListener.Stub() {
- @Override
- public void onWeakEscrowTokenActivated(long handle, int userId) {
- // Do nothing.
- }
- };
- }
-
- private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
- return new IWeakEscrowTokenRemovedListener.Stub() {
- @Override
- public void onWeakEscrowTokenRemoved(long handle, int userId) {
- // Do nothing.
- }
- };
- }
-}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 97b1ec7..2de305f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -639,4 +639,8 @@
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
+
+ <privapp-permissions package="com.android.devicediagnostics">
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ </privapp-permissions>
</permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c77004d..da91a96 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1867,6 +1867,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
+ "-483957611": {
+ "message": "Resuming configuration dispatch for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-481924678": {
"message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
"level": "DEBUG",
@@ -4021,6 +4027,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "1473051122": {
+ "message": "Pausing configuration dispatch for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1494644409": {
"message": " Rejecting as detached: %s",
"level": "VERBOSE",
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 471acaa..f1a6b69 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -80,3 +80,9 @@
},
},
}
+
+/////////////////////////////////
+// Move `fontchain_lint` to `core/tasks/fontchain_lint.mk`.
+// Because `system.img` is a dependency of `fontchain_lint`, it cannot be
+// converted to Android.bp for now.
+// After system.img can be generated by Soong, then it can be converted to Android.bp.
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
deleted file mode 100644
index a322b82..0000000
--- a/data/fonts/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2011 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Run sanity tests on fonts on checkbuild
-checkbuild: fontchain_lint
-
-FONTCHAIN_LINTER := $(HOST_OUT_EXECUTABLES)/fontchain_linter
-ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
-CHECK_EMOJI := false
-else
-CHECK_EMOJI := true
-endif
-
-fontchain_lint_timestamp := $(call intermediates-dir-for,PACKAGING,fontchain_lint)/stamp
-
-.PHONY: fontchain_lint
-fontchain_lint: $(fontchain_lint_timestamp)
-
-fontchain_lint_deps := \
- external/unicode/DerivedAge.txt \
- external/unicode/emoji-data.txt \
- external/unicode/emoji-sequences.txt \
- external/unicode/emoji-variation-sequences.txt \
- external/unicode/emoji-zwj-sequences.txt \
- external/unicode/additions/emoji-data.txt \
- external/unicode/additions/emoji-sequences.txt \
- external/unicode/additions/emoji-zwj-sequences.txt \
-
-$(fontchain_lint_timestamp): $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img $(fontchain_lint_deps)
- @echo Running fontchain lint
- $(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
- touch $@
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 14a4677..b2e5b75 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -42,10 +42,10 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
-import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.annotation.CallbackExecutor;
@@ -574,7 +574,8 @@
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@@ -1567,7 +1568,8 @@
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
- return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ return createEmptyContainer(wct, intent, taskId,
+ new ActivityStackAttributes.Builder().build(), launchingActivity,
null /* overlayTag */, null /* launchOptions */);
}
@@ -1581,8 +1583,9 @@
@Nullable
TaskFragmentContainer createEmptyContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
- @NonNull Rect bounds, @Nullable Activity launchingActivity,
- @Nullable String overlayTag, @Nullable Bundle launchOptions) {
+ @NonNull ActivityStackAttributes activityStackAttributes,
+ @Nullable Activity launchingActivity, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1605,43 +1608,21 @@
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
final TaskContainer taskContainer = container.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
+ final Rect taskBounds = taskContainer.getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
+ getMinDimensions(intent), taskBounds);
final int windowingMode = taskContainer
.getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
sanitizedBounds, windowingMode);
- mPresenter.updateAnimationParams(wct, taskFragmentToken,
- TaskFragmentAnimationParams.DEFAULT);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
- overlayTag != null && !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes,
+ getMinDimensions(intent));
return container;
}
/**
- * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
- * covered by the task bounds. Otherwise, returns {@code bounds}.
- */
- @NonNull
- private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
- @NonNull Rect taskBounds) {
- if (bounds.isEmpty()) {
- // Don't need to check if the bounds follows the task bounds.
- return bounds;
- }
- if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
- // Expand the bounds if the bounds are smaller than minimum dimensions.
- return new Rect();
- }
- if (!taskBounds.contains(bounds)) {
- // Expand the bounds if the bounds exceed the task bounds.
- return new Rect();
- }
- return bounds;
- }
-
- /**
* Returns a container for the new activity intent to launch into as splitting with the primary
* activity.
*/
@@ -1958,6 +1939,12 @@
return;
}
+ if (mActivityStackAttributesCalculator == null) {
+ Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container"
+ + " can not be updated.");
+ return;
+ }
+
if (mActivityStackAttributesCalculator != null) {
final ActivityStackAttributesCalculatorParams params =
new ActivityStackAttributesCalculatorParams(
@@ -1967,7 +1954,8 @@
container.getLaunchOptions());
final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
.apply(params);
- mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
}
}
@@ -2603,15 +2591,15 @@
mPresenter.createParentContainerInfoFromTaskProperties(
mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
// Fallback to expand the bounds if there's no activityStackAttributes calculator.
- final Rect relativeBounds = mActivityStackAttributesCalculator != null
- ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
- : new Rect();
- final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
- getMinDimensions(intent));
- // Expand the bounds if the requested bounds are smaller than minimum dimensions.
- if (shouldExpandContainer) {
- relativeBounds.setEmpty();
+ final ActivityStackAttributes attrs;
+ if (mActivityStackAttributesCalculator != null) {
+ attrs = mActivityStackAttributesCalculator.apply(params);
+ } else {
+ attrs = new ActivityStackAttributes.Builder().build();
+ Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay "
+ + "container as expected.");
}
+
final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
@@ -2631,20 +2619,8 @@
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
- // If there's an overlay container with the same tag and task ID, we treat
- // the OverlayCreateParams as the update to the container.
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- final TaskContainer taskContainer = overlayContainer.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics()
- .getBounds();
- final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
-
- mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
- final int windowingMode = taskContainer
- .getWindowingModeForTaskFragment(sanitizedBounds);
- mPresenter.updateWindowingMode(wct, overlayToken, windowingMode);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer,
- !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
// We can just return the updated overlay container and don't need to
// check other condition since we only have one OverlayCreateParams, and
// if the tag and task are matched, it's impossible to match another task
@@ -2654,7 +2630,7 @@
}
}
// Launch the overlay container to the task with taskId.
- return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+ return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
options);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 8b7fd10..2f2da8c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,8 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.PackageManager.MATCH_ALL;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
@@ -426,7 +424,8 @@
* creation has not been reported from the server yet.
*/
// TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
- private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+ @VisibleForTesting
+ void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@Nullable Rect relBounds) {
if (container.getInfo() == null) {
@@ -435,7 +434,8 @@
resizeTaskFragment(wct, container.getTaskFragmentToken(), relBounds);
}
- private void updateTaskFragmentWindowingModeIfRegistered(
+ @VisibleForTesting
+ void updateTaskFragmentWindowingModeIfRegistered(
@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@WindowingMode int windowingMode) {
@@ -579,13 +579,53 @@
super.setCompanionTaskFragment(wct, primary, secondary);
}
- void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
- final Rect bounds = attributes.getRelativeBounds();
+ void applyActivityStackAttributes(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ @NonNull ActivityStackAttributes attributes,
+ @Nullable Size minDimensions) {
+ final Rect taskBounds = container.getTaskContainer().getBounds();
+ final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
+ taskBounds);
+ final boolean isFillParent = relativeBounds.isEmpty();
+ final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+ final boolean dimOnTask = !isFillParent
+ && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+ && Flags.fullscreenDimFlag();
+ final IBinder fragmentToken = container.getTaskFragmentToken();
- resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
- updateWindowingMode(wct, container.getTaskFragmentToken(),
- bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
+ // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
+ // and WCT#setWindowingMode to take fragmentToken.
+ resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
+ int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment(
+ relativeBounds);
+ updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
+ // Always use default animation for standalone ActivityStack.
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+ setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
+ setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, minDimension)) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 71195b6..73109e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -126,6 +126,11 @@
}
@NonNull
+ Rect getBounds() {
+ return mConfiguration.windowConfiguration.getBounds();
+ }
+
+ @NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 4e7b760..34d43ad 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,14 +16,17 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -36,7 +39,6 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -56,6 +58,8 @@
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -266,62 +270,21 @@
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ public void testSanitizeBounds_smallerThanMinDimens_expandOverlay() {
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
-
final Rect bounds = new Rect(0, 0, 100, 100);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer =
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
- WINDOWING_MODE_UNDEFINED);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
- false);
+ SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+ TASK_BOUNDS);
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer =
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).updateWindowingMode(mTransaction,
- overlayToken, WINDOWING_MODE_UNDEFINED);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
- overlayContainer, false);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
+ SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
}
@Test
@@ -331,6 +294,7 @@
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer =
createOrUpdateOverlayTaskFragmentIfNeeded("test");
+ setupTaskFragmentInfo(overlayContainer, mActivity);
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
@@ -437,7 +401,7 @@
assertThrows(NullPointerException.class, () ->
mSplitController.updateActivityStackAttributes(new Binder(), null));
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
@@ -447,7 +411,7 @@
mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
new ActivityStackAttributes.Builder().build());
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
@@ -457,19 +421,20 @@
mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
new ActivityStackAttributes.Builder().build());
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
public void testUpdateActivityStackAttributes() {
final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
- doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
+ doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any(), any());
final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
final IBinder token = container.getTaskFragmentToken();
mSplitController.updateActivityStackAttributes(token, attrs);
- verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
+ verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs),
+ any());
}
@Test
@@ -521,6 +486,89 @@
.that(taskContainer.getOverlayContainer()).isNull();
}
+ @Test
+ public void testApplyActivityStackAttributesForExpandedContainer() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 200, 200))
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_MULTI_WINDOW);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForExpandedOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer_exceedsMinDimensions() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final Rect relativeBounds = new Rect(0, 0, 200, 200);
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(relativeBounds)
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ new Size(relativeBounds.width() + 1, relativeBounds.height()));
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ new Rect());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
new file mode 100644
index 0000000..4c76168
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleTaskViewTest {
+
+ private lateinit var bubbleTaskView: BubbleTaskView
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ @Before
+ fun setUp() {
+ val taskView = TaskView(context, mock<TaskViewTaskController>())
+ bubbleTaskView = BubbleTaskView(taskView, directExecutor())
+ }
+
+ @Test
+ fun onTaskCreated_updatesState() {
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(bubbleTaskView.taskId).isEqualTo(123)
+ assertThat(bubbleTaskView.componentName).isEqualTo(componentName)
+ assertThat(bubbleTaskView.isCreated).isTrue()
+ }
+
+ @Test
+ fun onTaskCreated_callsDelegateListener() {
+ var actualTaskId = -1
+ var actualComponentName: ComponentName? = null
+ val delegateListener = object : TaskView.Listener {
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ actualTaskId = taskId
+ actualComponentName = name
+ }
+ }
+ bubbleTaskView.delegateListener = delegateListener
+
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(actualTaskId).isEqualTo(123)
+ assertThat(actualComponentName).isEqualTo(componentName)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index f3fe895..9f7d0ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -74,7 +74,6 @@
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
@@ -146,7 +145,6 @@
private AlphaOptimizedButton mManageButton;
private TaskView mTaskView;
- private TaskViewTaskController mTaskViewTaskController;
private BubbleOverflowContainerView mOverflowView;
private int mTaskId = INVALID_TASK_ID;
@@ -434,7 +432,8 @@
* Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
* to be called after view inflate.
*/
- void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
+ void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
mController = controller;
mStackView = stackView;
mIsOverflow = isOverflow;
@@ -451,18 +450,22 @@
bringChildToFront(mOverflowView);
mManageButton.setVisibility(GONE);
} else {
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
// set a fixed width so it is not recalculated as part of a rotation. the width will be
// updated manually after the rotation.
FrameLayout.LayoutParams lp =
new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
mExpandedViewContainer.addView(mTaskView, lp);
bringChildToFront(mTaskView);
+ if (bubbleTaskView.isCreated()) {
+ mTaskViewListener.onTaskCreated(
+ bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+ }
}
}
@@ -876,7 +879,7 @@
return;
}
boolean isNew = mBubble == null || didBackingContentChange(bubble);
- if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
+ if (isNew || bubble.getKey().equals(mBubble.getKey())) {
mBubble = bubble;
mManageButton.setContentDescription(getResources().getString(
R.string.bubbles_settings_button_description, bubble.getAppName()));
@@ -1107,7 +1110,8 @@
* has been removed.
*
* If this view should be reused after this method is called, then
- * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
+ * {@link #initialize(BubbleController, BubbleStackView, boolean, BubbleTaskView)}
+ * must be invoked first.
*/
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 22e836a..e5d9ace 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -29,6 +29,7 @@
import android.view.LayoutInflater
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
@@ -57,10 +58,16 @@
/** Call before use and again if cleanUpExpandedState was called. */
fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
if (forBubbleBar) {
- createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
+ createBubbleBarExpandedView()
+ .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null)
} else {
createExpandedView()
- .initialize(controller, controller.stackView, true /* isOverflow */)
+ .initialize(
+ controller,
+ controller.stackView,
+ /* isOverflow= */ true,
+ /* bubbleTaskView= */ null
+ )
}
}
@@ -113,7 +120,10 @@
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(com.android.launcher3.icons.R.color.important_conversation),
+ ContextCompat.getColor(
+ context,
+ com.android.launcher3.icons.R.color.important_conversation
+ ),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
new file mode 100644
index 0000000..2fcd133
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.ComponentName
+import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.taskview.TaskView
+import java.util.concurrent.Executor
+
+/**
+ * A wrapper class around [TaskView] for bubble expanded views.
+ *
+ * [delegateListener] allows callers to change listeners after a task has been created.
+ */
+class BubbleTaskView(val taskView: TaskView, executor: Executor) {
+
+ /** Whether the task is already created. */
+ var isCreated = false
+ private set
+
+ /** The task id. */
+ var taskId = INVALID_TASK_ID
+ private set
+
+ /** The component name of the application running in the task. */
+ var componentName: ComponentName? = null
+ private set
+
+ /** [TaskView.Listener] for users of this class. */
+ var delegateListener: TaskView.Listener? = null
+
+ /** A [TaskView.Listener] that delegates to [delegateListener]. */
+ @get:VisibleForTesting
+ val listener = object : TaskView.Listener {
+ override fun onInitialized() {
+ delegateListener?.onInitialized()
+ }
+
+ override fun onReleased() {
+ delegateListener?.onReleased()
+ }
+
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ delegateListener?.onTaskCreated(taskId, name)
+ this@BubbleTaskView.taskId = taskId
+ isCreated = true
+ componentName = name
+ }
+
+ override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
+ delegateListener?.onTaskVisibilityChanged(taskId, visible)
+ }
+
+ override fun onTaskRemovalStarted(taskId: Int) {
+ delegateListener?.onTaskRemovalStarted(taskId)
+ }
+
+ override fun onBackPressedOnTaskRoot(taskId: Int) {
+ delegateListener?.onBackPressedOnTaskRoot(taskId)
+ }
+ }
+
+ init {
+ taskView.setListener(executor, listener)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index f6c382f..5855a81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -35,10 +35,7 @@
import androidx.annotation.Nullable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
/**
* Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
@@ -65,7 +62,6 @@
private final Context mContext;
private final BubbleController mController;
- private final @ShellMainThread ShellExecutor mMainExecutor;
private final BubbleTaskViewHelper.Listener mListener;
private final View mParentView;
@@ -73,7 +69,6 @@
private Bubble mBubble;
@Nullable
private PendingIntent mPendingIntent;
- private TaskViewTaskController mTaskViewTaskController;
@Nullable
private TaskView mTaskView;
private int mTaskId = INVALID_TASK_ID;
@@ -204,17 +199,18 @@
public BubbleTaskViewHelper(Context context,
BubbleController controller,
BubbleTaskViewHelper.Listener listener,
+ BubbleTaskView bubbleTaskView,
View parent) {
mContext = context;
mController = controller;
- mMainExecutor = mController.getMainExecutor();
mListener = listener;
mParentView = parent;
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
+ if (bubbleTaskView.isCreated()) {
+ mTaskId = bubbleTaskView.getTaskId();
+ mListener.onTaskCreated();
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index bb30c5e..c3d899e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -46,6 +46,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -173,10 +175,12 @@
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
+ BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
- info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
+ info.bubbleBarExpandedView.initialize(
+ controller, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -201,9 +205,11 @@
R.layout.bubble_view, stackView, false /* attachToRoot */);
info.imageView.initialize(controller.getPositioner());
+ BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.initialize(controller, stackView, false /* isOverflow */);
+ info.expandedView.initialize(
+ controller, stackView, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -219,6 +225,15 @@
}
return info;
}
+
+ private static BubbleTaskView createBubbleTaskView(
+ Context context, BubbleController controller) {
+ TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
+ controller.getTaskOrganizer(),
+ controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
+ TaskView taskView = new TaskView(context, taskViewTaskController);
+ return new BubbleTaskView(taskView, controller.getMainExecutor());
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 66c0c96..3cf23ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.bar;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
@@ -27,6 +29,7 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
@@ -35,6 +38,7 @@
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
@@ -130,7 +134,8 @@
}
/** Set the BubbleController on the view, must be called before doing anything else. */
- public void initialize(BubbleController controller, boolean isOverflow) {
+ public void initialize(BubbleController controller, boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
mController = controller;
mIsOverflow = isOverflow;
@@ -140,14 +145,19 @@
mOverflowView.setBubbleController(mController);
addView(mOverflowView);
} else {
-
+ mTaskView = bubbleTaskView.getTaskView();
mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
- /* listener= */ this,
+ /* listener= */ this, bubbleTaskView,
/* viewParent= */ this);
- mTaskView = mBubbleTaskViewHelper.getTaskView();
- addView(mTaskView);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
+ FrameLayout.LayoutParams lp =
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ addView(mTaskView, lp);
mTaskView.setEnableSurfaceClipping(true);
mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setVisibility(VISIBLE);
// Handle view needs to draw on top of task view.
bringChildToFront(mHandleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea..1232baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1020,7 +1020,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsController.finishInner: no valid PiP leash;"
+ "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
- mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ mPipTransaction, mPipTask, mPipTaskId);
} else {
t.show(pipLeash);
PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5a74255..e6faa63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,7 +93,7 @@
// On a smaller screen, don't require as much empty space on screen, as offscreen
// drags will be restricted too much.
- final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600
? R.dimen.freeform_required_visible_empty_space_in_header :
R.dimen.small_screen_required_visible_empty_space_in_header;
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8d..0d18535 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -78,6 +78,14 @@
uiAutomation.dropShellPermissionIdentity()
}
+ override fun onProcessStarted(
+ pid: Int,
+ processUid: Int,
+ packageUid: Int,
+ packageName: String,
+ processName: String
+ ) {}
+
override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java
index b3e5c52..61c9131 100644
--- a/media/java/android/media/LoudnessCodecController.java
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -32,12 +32,13 @@
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
/**
* Class for getting recommended loudness parameter updates for audio decoders as they are used
@@ -320,11 +321,6 @@
* Stops any loudness updates and frees up the resources.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void release() {
- close();
- }
-
- /** @hide */
@Override
public void close() {
synchronized (mControllerLock) {
@@ -339,9 +335,12 @@
}
/** @hide */
- /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
+ /*package*/ void mediaCodecsConsume(
+ Consumer<Entry<LoudnessCodecInfo, Set<MediaCodec>>> consumer) {
synchronized (mControllerLock) {
- return mMediaCodecs;
+ for (Entry<LoudnessCodecInfo, Set<MediaCodec>> entry : mMediaCodecs.entrySet()) {
+ consumer.accept(entry);
+ }
}
}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index 46be54b..fa08658 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -32,7 +32,6 @@
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
@@ -81,16 +80,15 @@
mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
// send the appropriate bundle for the user to update
if (lcConfig.getSessionId() == sessionId) {
- final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
- lcConfig.getRegisteredMediaCodecs();
- for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
+ lcConfig.mediaCodecsConsume(mcEntry -> {
+ final LoudnessCodecInfo codecInfo = mcEntry.getKey();
final String infoKey = Integer.toString(codecInfo.hashCode());
Bundle bundle = null;
if (params.containsKey(infoKey)) {
bundle = new Bundle(params.getPersistableBundle(infoKey));
}
- final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo);
+ final Set<MediaCodec> mediaCodecs = mcEntry.getValue();
for (MediaCodec mediaCodec : mediaCodecs) {
final String mediaCodecKey = Integer.toString(
mediaCodec.hashCode());
@@ -121,7 +119,7 @@
break;
}
}
- }
+ });
}
return lcConfig;
});
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index caddd8a..2b31bfe 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -33,6 +33,7 @@
import android.media.AudioFormat.Encoding;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
+import android.media.tv.ad.TvAdManager;
import android.media.tv.interactive.TvInteractiveAppManager;
import android.net.Uri;
import android.os.Binder;
@@ -2744,6 +2745,7 @@
private int mVideoHeight;
private TvInteractiveAppManager.Session mIAppSession;
+ private TvAdManager.Session mAdSession;
private boolean mIAppNotificationEnabled = false;
private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
@@ -2764,6 +2766,14 @@
this.mIAppSession = iAppSession;
}
+ public TvAdManager.Session getAdSession() {
+ return mAdSession;
+ }
+
+ public void setAdSession(TvAdManager.Session adSession) {
+ this.mAdSession = adSession;
+ }
+
/**
* Releases this session.
*/
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index a747e49..9620065 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,9 +16,13 @@
package android.media.tv.ad;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManagerCallback;
import android.media.tv.ad.TvAdServiceInfo;
+import android.net.Uri;
+import android.os.Bundle;
import android.view.Surface;
/**
@@ -31,10 +35,27 @@
in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
void startAdService(in IBinder sessionToken, int userId);
+ void stopAdService(in IBinder sessionToken, int userId);
+ void resetAdService(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
+ void sendCurrentVideoBounds(in IBinder sessionToken, in Rect bounds, int userId);
+ void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
+ void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
+ void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId);
+ void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
+ int userId);
+
+ void notifyError(in IBinder sessionToken, in String errMsg, in Bundle params, int userId);
+ void notifyTvMessage(in IBinder sessionToken, in int type, in Bundle data, int userId);
+
void registerCallback(in ITvAdManagerCallback callback, int userId);
void unregisterCallback(in ITvAdManagerCallback callback, int userId);
+
+ void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeMediaView(in IBinder sessionToken, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 751257c..69afb17 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,6 +16,10 @@
package android.media.tv.ad;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
import android.view.Surface;
/**
@@ -25,6 +29,21 @@
oneway interface ITvAdSession {
void release();
void startAdService();
+ void stopAdService();
+ void resetAdService();
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
+
+ void sendCurrentVideoBounds(in Rect bounds);
+ void sendCurrentChannelUri(in Uri channelUri);
+ void sendTrackInfoList(in List<TvTrackInfo> tracks);
+ void sendCurrentTvInputId(in String inputId);
+ void sendSigningResult(in String signingId, in byte[] result);
+
+ void notifyError(in String errMsg, in Bundle params);
+ void notifyTvMessage(int type, in Bundle data);
+
+ void createMediaView(in IBinder windowToken, in Rect frame);
+ void relayoutMediaView(in Rect frame);
+ void removeMediaView();
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 4df2783..251351d 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -16,7 +16,14 @@
package android.media.tv.ad;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -29,6 +36,8 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.util.List;
+
/**
* Implements the internal ITvAdSession interface.
* @hide
@@ -43,6 +52,19 @@
private static final int DO_RELEASE = 1;
private static final int DO_SET_SURFACE = 2;
private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+ private static final int DO_CREATE_MEDIA_VIEW = 4;
+ private static final int DO_RELAYOUT_MEDIA_VIEW = 5;
+ private static final int DO_REMOVE_MEDIA_VIEW = 6;
+ private static final int DO_START_AD_SERVICE = 7;
+ private static final int DO_STOP_AD_SERVICE = 8;
+ private static final int DO_RESET_AD_SERVICE = 9;
+ private static final int DO_SEND_CURRENT_VIDEO_BOUNDS = 10;
+ private static final int DO_SEND_CURRENT_CHANNEL_URI = 11;
+ private static final int DO_SEND_TRACK_INFO_LIST = 12;
+ private static final int DO_SEND_CURRENT_TV_INPUT_ID = 13;
+ private static final int DO_SEND_SIGNING_RESULT = 14;
+ private static final int DO_NOTIFY_ERROR = 15;
+ private static final int DO_NOTIFY_TV_MESSAGE = 16;
private final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -61,6 +83,7 @@
@Override
public void release() {
+ mSessionImpl.scheduleMediaViewCleanup();
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
}
@@ -97,6 +120,66 @@
args.recycle();
break;
}
+ case DO_CREATE_MEDIA_VIEW: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_RELAYOUT_MEDIA_VIEW: {
+ mSessionImpl.relayoutMediaView((Rect) msg.obj);
+ break;
+ }
+ case DO_REMOVE_MEDIA_VIEW: {
+ mSessionImpl.removeMediaView(true);
+ break;
+ }
+ case DO_START_AD_SERVICE: {
+ mSessionImpl.startAdService();
+ break;
+ }
+ case DO_STOP_AD_SERVICE: {
+ mSessionImpl.stopAdService();
+ break;
+ }
+ case DO_RESET_AD_SERVICE: {
+ mSessionImpl.resetAdService();
+ break;
+ }
+ case DO_SEND_CURRENT_VIDEO_BOUNDS: {
+ mSessionImpl.sendCurrentVideoBounds((Rect) msg.obj);
+ break;
+ }
+ case DO_SEND_CURRENT_CHANNEL_URI: {
+ mSessionImpl.sendCurrentChannelUri((Uri) msg.obj);
+ break;
+ }
+ case DO_SEND_TRACK_INFO_LIST: {
+ mSessionImpl.sendTrackInfoList((List<TvTrackInfo>) msg.obj);
+ break;
+ }
+ case DO_SEND_CURRENT_TV_INPUT_ID: {
+ mSessionImpl.sendCurrentTvInputId((String) msg.obj);
+ break;
+ }
+ case DO_SEND_SIGNING_RESULT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_NOTIFY_ERROR: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyError((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_NOTIFY_TV_MESSAGE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyTvMessage((Integer) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -115,7 +198,17 @@
@Override
public void startAdService() throws RemoteException {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_AD_SERVICE));
+ }
+ @Override
+ public void stopAdService() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_AD_SERVICE));
+ }
+
+ @Override
+ public void resetAdService() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RESET_AD_SERVICE));
}
@Override
@@ -129,6 +222,64 @@
mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
}
+ @Override
+ public void sendCurrentVideoBounds(@Nullable Rect bounds) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_CURRENT_VIDEO_BOUNDS, bounds));
+ }
+
+ @Override
+ public void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_CURRENT_CHANNEL_URI, channelUri));
+ }
+
+ @Override
+ public void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_TRACK_INFO_LIST, tracks));
+ }
+
+ @Override
+ public void sendCurrentTvInputId(@Nullable String inputId) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_CURRENT_TV_INPUT_ID, inputId));
+ }
+
+ @Override
+ public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_SEND_SIGNING_RESULT, signingId, result));
+ }
+
+ @Override
+ public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
+ }
+
+ @Override
+ public void notifyTvMessage(int type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
+ }
+
+ @Override
+ public void createMediaView(IBinder windowToken, Rect frame) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
+ }
+
+ @Override
+ public void relayoutMediaView(Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_MEDIA_VIEW, frame));
+ }
+
+ @Override
+ public void removeMediaView() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
+ }
+
private final class TvAdEventReceiver extends InputEventReceiver {
TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 9c75051..4dce72f 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -21,8 +21,12 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.graphics.Rect;
import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
import android.media.tv.flags.Flags;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -35,6 +39,7 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
+import android.view.View;
import com.android.internal.util.Preconditions;
@@ -250,6 +255,14 @@
mSessionCallbackRecordMap = sessionCallbackRecordMap;
}
+ public TvInputManager.Session getInputSession() {
+ return mInputSession;
+ }
+
+ public void setInputSession(TvInputManager.Session inputSession) {
+ mInputSession = inputSession;
+ }
+
/**
* Releases this session.
*/
@@ -286,6 +299,67 @@
}
/**
+ * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeMediaView()} should be called to remove the media view.
+ * Since a session can have only one media view, this method should be called only once
+ * or it can be called again after calling {@link #removeMediaView()}.
+ *
+ * @param view A view for AD service.
+ * @param frame A position of the media view.
+ * @throws IllegalStateException if {@code view} is not attached to a window.
+ */
+ void createMediaView(@NonNull View view, @NonNull Rect frame) {
+ Preconditions.checkNotNull(view);
+ Preconditions.checkNotNull(frame);
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(@NonNull Rect frame) {
+ Preconditions.checkNotNull(frame);
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.relayoutMediaView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.removeMediaView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies of any structural changes (format or size) of the surface passed in
* {@link #setSurface}.
*
@@ -348,6 +422,117 @@
}
}
+ void stopAdService() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.stopAdService(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void resetAdService() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.resetAdService(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendCurrentVideoBounds(@NonNull Rect bounds) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentChannelUri(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendTrackInfoList(mToken, tracks, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendCurrentTvInputId(@Nullable String inputId) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCurrentTvInputId(mToken, inputId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendSigningResult(mToken, signingId, result, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyError(mToken, errMsg, params, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies AD service session when a new TV message is received.
+ */
+ public void notifyTvMessage(int type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvMessage(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6995703..5d81837 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -20,19 +20,28 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Px;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -42,6 +51,7 @@
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import com.android.internal.os.SomeArgs;
@@ -56,6 +66,8 @@
private static final boolean DEBUG = false;
private static final String TAG = "TvAdService";
+ private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
/**
* Name under which a TvAdService component publishes information about itself. This meta-data
* must reference an XML resource containing an
@@ -151,7 +163,14 @@
private final Context mContext;
final Handler mHandler;
private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
+ private FrameLayout mMediaViewContainer;
+ private View mMediaView;
+ private MediaViewCleanUpTask mMediaViewCleanUpTask;
+ private boolean mMediaViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mMediaFrame;
/**
@@ -166,6 +185,48 @@
}
/**
+ * Enables or disables the media view.
+ *
+ * <p>By default, the media view is disabled. Must be called explicitly after the
+ * session is created to enable the media view.
+ *
+ * <p>The TV AD service can disable its media view when needed.
+ *
+ * @param enable {@code true} if you want to enable the media view. {@code false}
+ * otherwise.
+ * @hide
+ */
+ @CallSuper
+ public void setMediaViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mMediaViewEnabled) {
+ return;
+ }
+ mMediaViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createMediaView(mWindowToken, mMediaFrame);
+ }
+ } else {
+ removeMediaView(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns {@code true} if media view is enabled, {@code false} otherwise.
+ *
+ * @see #setMediaViewEnabled(boolean)
+ * @hide
+ */
+ public boolean isMediaViewEnabled() {
+ return mMediaViewEnabled;
+ }
+
+ /**
* Releases TvAdService session.
*/
public abstract void onRelease();
@@ -180,18 +241,44 @@
mSessionCallback = null;
mPendingActions.clear();
}
+ // Removes the media view lastly so that any hanging on the main thread can be handled
+ // in {@link #scheduleMediaViewCleanup}.
+ removeMediaView(true);
}
/**
* Starts TvAdService session.
+ * @hide
*/
public void onStartAdService() {
}
+ /**
+ * Stops TvAdService session.
+ * @hide
+ */
+ public void onStopAdService() {
+ }
+
+ /**
+ * Resets TvAdService session.
+ * @hide
+ */
+ public void onResetAdService() {
+ }
+
void startAdService() {
onStartAdService();
}
+ void stopAdService() {
+ onStopAdService();
+ }
+
+ void resetAdService() {
+ onResetAdService();
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -307,6 +394,109 @@
}
/**
+ * Receives current video bounds.
+ *
+ * @param bounds the rectangle area for rendering the current video.
+ * @hide
+ */
+ public void onCurrentVideoBounds(@NonNull Rect bounds) {
+ }
+
+ /**
+ * Receives current channel URI.
+ * @hide
+ */
+ public void onCurrentChannelUri(@Nullable Uri channelUri) {
+ }
+
+ /**
+ * Receives track list.
+ * @hide
+ */
+ public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ }
+
+ /**
+ * Receives current TV input ID.
+ * @hide
+ */
+ public void onCurrentTvInputId(@Nullable String inputId) {
+ }
+
+ /**
+ * Receives signing result.
+ *
+ * @param signingId the ID to identify the request. It's the same as the corresponding ID in
+ * {@link Session#requestSigning(String, String, String, byte[])}
+ * @param result the signed result.
+ *
+ * @see #requestSigning(String, String, String, byte[])
+ * @hide
+ */
+ public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ }
+
+ /**
+ * Called when the application sends information of an error.
+ *
+ * @param errMsg the message of the error.
+ * @param params additional parameters of the error. For example, the signingId of {@link
+ * TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])}
+ * can be included to identify the related signing request, and the method name
+ * "onRequestSigning" can also be added to the params.
+ *
+ * @see TvAdView#ERROR_KEY_METHOD_NAME
+ * @hide
+ */
+ public void onError(@NonNull String errMsg, @NonNull Bundle params) {
+ }
+
+ /**
+ * Called when a TV message is received
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
+ * @hide
+ */
+ public void onTvMessage(@TvInputManager.TvMessageType int type,
+ @NonNull Bundle data) {
+ }
+
+ /**
+ * Called when the size of the media view is changed by the application.
+ *
+ * <p>This is always called at least once when the session is created regardless of whether
+ * the media view is enabled or not. The media view container size is the same as the
+ * containing {@link TvAdView}. Note that the size of the underlying surface can
+ * be different if the surface was changed by calling {@link #layoutSurface}.
+ *
+ * @param width The width of the media view, in pixels.
+ * @param height The height of the media view, in pixels.
+ * @hide
+ */
+ public void onMediaViewSizeChanged(@Px int width, @Px int height) {
+ }
+
+ /**
+ * Called when the application requests to create a media view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the media window. {@code null} if no media view is created.
+ * @hide
+ */
+ @Nullable
+ public View onCreateMediaView() {
+ return null;
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -373,6 +563,37 @@
onSurfaceChanged(format, width, height);
}
+ void sendCurrentVideoBounds(@NonNull Rect bounds) {
+ onCurrentVideoBounds(bounds);
+ }
+
+ void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ onCurrentChannelUri(channelUri);
+ }
+
+ void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+ onTrackInfoList(tracks);
+ }
+
+ void sendCurrentTvInputId(@Nullable String inputId) {
+ onCurrentTvInputId(inputId);
+ }
+
+ void sendSigningResult(String signingId, byte[] result) {
+ onSigningResult(signingId, result);
+ }
+
+ void notifyError(String errMsg, Bundle params) {
+ onError(errMsg, params);
+ }
+
+ void notifyTvMessage(int type, Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")");
+ }
+ onTvMessage(type, data);
+ }
+
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
@@ -388,6 +609,137 @@
}
}
}
+
+ /**
+ * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
+ * to the media window.
+ *
+ * @param windowToken A window token of the application.
+ * @param frame A position of the media view.
+ */
+ void createMediaView(IBinder windowToken, Rect frame) {
+ if (mMediaViewContainer != null) {
+ removeMediaView(false);
+ }
+ if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+ mWindowToken = windowToken;
+ mMediaFrame = frame;
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ if (!mMediaViewEnabled) {
+ return;
+ }
+ mMediaView = onCreateMediaView();
+ if (mMediaView == null) {
+ return;
+ }
+ if (mMediaViewCleanUpTask != null) {
+ mMediaViewCleanUpTask.cancel(true);
+ mMediaViewCleanUpTask = null;
+ }
+ // Creates a container view to check hanging on the media view detaching.
+ // Adding/removing the media view to/from the container make the view attach/detach
+ // logic run on the main thread.
+ mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+ mMediaViewContainer.addView(mMediaView);
+
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(Rect frame) {
+ if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+ if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+ || mMediaFrame.height() != frame.height()) {
+ // Note: relayoutMediaView is called whenever TvAdView's layout is
+ // changed regardless of setMediaViewEnabled.
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ }
+ mMediaFrame = frame;
+ if (!mMediaViewEnabled || mMediaViewContainer == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView(boolean clearWindowToken) {
+ if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mMediaFrame = null;
+ }
+ if (mMediaViewContainer != null) {
+ // Removes the media view from the view hierarchy in advance so that it can be
+ // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+ // hanging.
+ mMediaViewContainer.removeView(mMediaView);
+ mMediaView = null;
+ mWindowManager.removeView(mMediaViewContainer);
+ mMediaViewContainer = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Schedules a task which checks whether the media view is detached and kills the process
+ * if it is not. Note that this method is expected to be called in a non-main thread.
+ */
+ void scheduleMediaViewCleanup() {
+ View mediaViewParent = mMediaViewContainer;
+ if (mediaViewParent != null) {
+ mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+ mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ mediaViewParent);
+ }
+ }
+ }
+
+ private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+ @Override
+ protected Void doInBackground(View... views) {
+ View mediaViewParent = views[0];
+ try {
+ Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ if (isCancelled()) {
+ return null;
+ }
+ if (mediaViewParent.isAttachedToWindow()) {
+ Log.e(TAG, "Time out on releasing media view. Killing "
+ + mediaViewParent.getContext().getPackageName());
+ android.os.Process.killProcess(Process.myPid());
+ }
+ return null;
+ }
}
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 5e67fe9..ec23b7c 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,12 +16,20 @@
package android.media.tv.ad;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
@@ -32,6 +40,9 @@
import android.view.View;
import android.view.ViewGroup;
+import java.util.List;
+import java.util.concurrent.Executor;
+
/**
* Displays contents of TV AD services.
* @hide
@@ -40,11 +51,22 @@
private static final String TAG = "TvAdView";
private static final boolean DEBUG = false;
+ /**
+ * The name of the method where the error happened, if applicable. For example, if there is an
+ * error during signing, the request name is "onRequestSigning".
+ * @see #notifyError(String, Bundle)
+ * @hide
+ */
+ public static final String ERROR_KEY_METHOD_NAME = "method_name";
+
private final TvAdManager mTvAdManager;
private final Handler mHandler = new Handler();
+ private final Object mCallbackLock = new Object();
private TvAdManager.Session mSession;
private MySessionCallback mSessionCallback;
+ private TvAdCallback mCallback;
+ private Executor mCallbackExecutor;
private final AttributeSet mAttrs;
private final int mDefStyleAttr;
@@ -64,6 +86,9 @@
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
+ private boolean mMediaViewCreated;
+ private Rect mMediaViewFrame;
+
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@@ -121,6 +146,51 @@
mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
}
+ /**
+ * Sets the TvAdView to receive events from TvInputService. This method links the session of
+ * TvAdManager to TvInputManager session, so the TvAdService can get the TvInputService events.
+ *
+ * @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
+ * to unlink the TvView.
+ * @return {@code true} if it's linked successfully; {@code false} otherwise.
+ * @hide
+ */
+ public boolean setTvView(@Nullable TvView tvView) {
+ if (tvView == null) {
+ return unsetTvView();
+ }
+ TvInputManager.Session inputSession = tvView.getInputSession();
+ if (inputSession == null || mSession == null) {
+ return false;
+ }
+ mSession.setInputSession(inputSession);
+ inputSession.setAdSession(mSession);
+ return true;
+ }
+
+ private boolean unsetTvView() {
+ if (mSession == null || mSession.getInputSession() == null) {
+ return false;
+ }
+ mSession.getInputSession().setAdSession(null);
+ mSession.setInputSession(null);
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionMediaView();
+ }
+
+ /** @hide */
+ @Override
+ public void onDetachedFromWindow() {
+ removeSessionMediaView();
+ super.onDetachedFromWindow();
+ }
+
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
@@ -150,6 +220,11 @@
public void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ createSessionMediaView();
+ } else {
+ removeSessionMediaView();
+ }
}
private void resetSurfaceView() {
@@ -162,6 +237,7 @@
@Override
protected void updateSurface() {
super.updateSurface();
+ relayoutSessionMediaView();
}};
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
@@ -174,6 +250,69 @@
addView(mSurfaceView);
}
+ /**
+ * Resets this TvAdView to release its resources.
+ *
+ * <p>It can be reused by call {@link #prepareAdService(String, String)}.
+ * @hide
+ */
+ public void reset() {
+ if (DEBUG) Log.d(TAG, "reset()");
+ resetInternal();
+ }
+
+ private void resetInternal() {
+ mSessionCallback = null;
+ if (mSession != null) {
+ setSessionSurface(null);
+ removeSessionMediaView();
+ mUseRequestedSurfaceLayout = false;
+ mSession.release();
+ mSession = null;
+ resetSurfaceView();
+ }
+ }
+
+ private void createSessionMediaView() {
+ // TODO: handle z-order
+ if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+ return;
+ }
+ mMediaViewFrame = getViewFrameOnScreen();
+ mSession.createMediaView(this, mMediaViewFrame);
+ mMediaViewCreated = true;
+ }
+
+ private void removeSessionMediaView() {
+ if (mSession == null || !mMediaViewCreated) {
+ return;
+ }
+ mSession.removeMediaView();
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
+ }
+
+ private void relayoutSessionMediaView() {
+ if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mMediaViewFrame)) {
+ return;
+ }
+ mSession.relayoutMediaView(viewFrame);
+ mMediaViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ Rect frame = new Rect();
+ getGlobalVisibleRect(frame);
+ RectF frameF = new RectF(frame);
+ getMatrix().mapRect(frameF);
+ frameF.round(frame);
+ return frame;
+ }
+
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
@@ -185,7 +324,7 @@
if (mSession == null) {
return;
}
- //mSession.dispatchSurfaceChanged(format, width, height);
+ mSession.dispatchSurfaceChanged(format, width, height);
}
/**
@@ -205,16 +344,196 @@
/**
* Starts the AD service.
+ * @hide
*/
public void startAdService() {
if (DEBUG) {
- Log.d(TAG, "start");
+ Log.d(TAG, "startAdService");
}
if (mSession != null) {
mSession.startAdService();
}
}
+ /**
+ * Stops the AD service.
+ */
+ public void stopAdService() {
+ if (DEBUG) {
+ Log.d(TAG, "stopAdService");
+ }
+ if (mSession != null) {
+ mSession.stopAdService();
+ }
+ }
+
+ /**
+ * Resets the AD service.
+ *
+ * <p>This releases the resources of the corresponding {@link TvAdService.Session}.
+ */
+ public void resetAdService() {
+ if (DEBUG) {
+ Log.d(TAG, "resetAdService");
+ }
+ if (mSession != null) {
+ mSession.resetAdService();
+ }
+ }
+
+ /**
+ * Sends current video bounds to related TV AD service.
+ *
+ * @param bounds the rectangle area for rendering the current video.
+ */
+ public void sendCurrentVideoBounds(@NonNull Rect bounds) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentVideoBounds");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentVideoBounds(bounds);
+ }
+ }
+
+ /**
+ * Sends current channel URI to related TV AD service.
+ *
+ * @param channelUri The current channel URI; {@code null} if there is no currently tuned
+ * channel.
+ */
+ public void sendCurrentChannelUri(@Nullable Uri channelUri) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentChannelUri");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentChannelUri(channelUri);
+ }
+ }
+
+ /**
+ * Sends track info list to related TV AD service.
+ */
+ public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
+ if (DEBUG) {
+ Log.d(TAG, "sendTrackInfoList");
+ }
+ if (mSession != null) {
+ mSession.sendTrackInfoList(tracks);
+ }
+ }
+
+ /**
+ * Sends current TV input ID to related TV AD service.
+ *
+ * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
+ * tuned.
+ * @see android.media.tv.TvInputInfo
+ */
+ public void sendCurrentTvInputId(@Nullable String inputId) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCurrentTvInputId");
+ }
+ if (mSession != null) {
+ mSession.sendCurrentTvInputId(inputId);
+ }
+ }
+
+ /**
+ * Sends signing result to related TV AD service.
+ *
+ * <p>This is used when the corresponding server of the ADs requires signing during handshaking,
+ * and the AD service doesn't have the built-in private key. The private key is provided by the
+ * content providers and pre-built in the related app, such as TV app.
+ *
+ * @param signingId the ID to identify the request. It's the same as the corresponding ID in
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])}
+ * @param result the signed result.
+ * @hide
+ */
+ public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+ if (DEBUG) {
+ Log.d(TAG, "sendSigningResult");
+ }
+ if (mSession != null) {
+ mSession.sendSigningResult(signingId, result);
+ }
+ }
+
+ /**
+ * Notifies the corresponding {@link TvAdService} when there is an error.
+ *
+ * @param errMsg the message of the error.
+ * @param params additional parameters of the error. For example, the signingId of {@link
+ * TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])} can be
+ * included to identify the related signing request, and the method name "onRequestSigning"
+ * can also be added to the params.
+ *
+ * @see #ERROR_KEY_METHOD_NAME
+ */
+ public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyError msg=" + errMsg + "; params=" + params);
+ }
+ if (mSession != null) {
+ mSession.notifyError(errMsg, params);
+ }
+ }
+
+ /**
+ * This is called to notify the corresponding TV AD service when a new TV message is received.
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message. The bundle keys are:
+ * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
+ * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+ * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+ * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+ * how to parse this data.
+ */
+ public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
+ @NonNull Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTvMessage type=" + type
+ + "; data=" + data);
+ }
+ if (mSession != null) {
+ mSession.notifyTvMessage(type, data);
+ }
+ }
+
+ /**
+ * Sets the callback to be invoked when an event is dispatched to this TvAdView.
+ *
+ * @param callback the callback to receive events. MUST NOT be {@code null}.
+ *
+ * @see #clearCallback()
+ * @hide
+ */
+ public void setCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TvAdCallback callback) {
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, callback);
+ synchronized (mCallbackLock) {
+ mCallbackExecutor = executor;
+ mCallback = callback;
+ }
+ }
+
+ /**
+ * Clears the callback.
+ *
+ * @see #setCallback(Executor, TvAdCallback)
+ * @hide
+ */
+ public void clearCallback() {
+ synchronized (mCallbackLock) {
+ mCallback = null;
+ mCallbackExecutor = null;
+ }
+ }
+
private class MySessionCallback extends TvAdManager.SessionCallback {
final String mServiceId;
@@ -246,6 +565,7 @@
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
+ createSessionMediaView();
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -262,6 +582,8 @@
Log.w(TAG, "onSessionReleased - session not created");
return;
}
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
mSessionCallback = null;
mSession = null;
}
@@ -285,4 +607,11 @@
requestLayout();
}
}
+
+ /**
+ * Callback used to receive various status updates on the {@link TvAdView}.
+ * @hide
+ */
+ public abstract static class TvAdCallback {
+ }
}
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 52060f1..66fec1c 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -35,10 +35,9 @@
// In R, we change this to true, as it is the correct way per SoundPool documentation.
static constexpr bool kStealActiveStream_OldestFirst = true;
-// kPlayOnCallingThread = true prior to R.
// Changing to false means calls to play() are almost instantaneous instead of taking around
// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = true;
+static constexpr bool kPlayOnCallingThread = false;
// Amount of time for a StreamManager thread to wait before closing.
static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index adbab4b..340b49b 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -48,7 +48,7 @@
public:
JavaThread(std::function<void()> f, const char *name)
: mF{std::move(f)} {
- createThreadEtc(staticFunction, this, name);
+ createThreadEtc(staticFunction, this, name, ANDROID_PRIORITY_AUDIO);
}
JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 25040a9..e872a58 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -86,7 +86,7 @@
}
// Retrieves the associated object, returns nullValue T if not available.
- T get(JNIEnv *env, jobject thiz) {
+ T get(JNIEnv *env, jobject thiz) const {
std::lock_guard lg(mLock);
// NOLINTNEXTLINE(performance-no-int-to-ptr)
auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId));
@@ -167,8 +167,10 @@
// is possible by checking if the WeakGlobalRef is null equivalent.
auto& getSoundPoolManager() {
- static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext);
- return soundPoolManager;
+ // never-delete singleton
+ static auto soundPoolManager =
+ new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext);
+ return *soundPoolManager;
}
inline auto getSoundPool(JNIEnv *env, jobject thiz) {
@@ -274,8 +276,9 @@
auto& getSoundPoolJavaRefManager() {
// Note this can store shared_ptrs to either jweak and jobject,
// as the underlying type is identical.
- static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
- return concurrentHashMap;
+ static auto concurrentHashMap =
+ new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>();
+ return *concurrentHashMap;
}
// make_shared_globalref_from_localref() creates a sharable Java global
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
index 4f6ede5..46256ba 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -126,7 +126,7 @@
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.release(); // stops updats
+ mLcc.close(); // stops updates
verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
} finally {
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 5becc86..f13402c 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -23,7 +23,7 @@
android:shape="rectangle"
android:top="1dp">
<shape>
- <corners android:radius="16dp" />
+ <corners android:radius="4dp" />
<solid android:color="@color/dropdown_container" />
</shape>
</item>
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 0000000..d7b509e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:color="@android:color/transparent">
+ <item
+ android:bottom="1dp"
+ android:shape="rectangle"
+ android:top="1dp">
+ <shape>
+ <corners android:bottomLeftRadius="4dp"
+ android:bottomRightRadius="4dp"/>
+ <solid android:color="@color/sign_in_options_container" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 0000000..929756c
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+ android:elevation="3dp">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:contentDescription="@string/provider_icon_content_description"
+ android:background="@null"/>
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+ style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index cb6c6b4..1fe5e0e 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,22 +17,25 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="@dimen/autofill_dropdown_layout_width"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
<ImageView
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
- android:layout_width="@dimen/autofill_dropdown_text_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextTitle"/>
<TextView
android:id="@android:id/text2"
@@ -40,6 +43,8 @@
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextSubtitle"/>
</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index dcb7ef9..7cb1d01 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -20,4 +20,6 @@
<color name="text_primary">#1A1B20</color>
<color name="text_secondary">#44474F</color>
<color name="dropdown_container">#F3F3FA</color>
+ <color name="sign_in_options_container">#DADADA</color>
+ <color name="sign_in_options_icon_color">#1B1B1B</color>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 2a4719d..3a8c78f 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,11 +18,13 @@
<resources>
<dimen name="autofill_view_top_padding">12dp</dimen>
- <dimen name="autofill_view_right_padding">24dp</dimen>
+ <dimen name="autofill_view_right_padding">12dp</dimen>
<dimen name="autofill_view_bottom_padding">12dp</dimen>
<dimen name="autofill_view_left_padding">16dp</dimen>
<dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
<dimen name="autofill_icon_size">24dp</dimen>
- <dimen name="autofill_dropdown_layout_width">296dp</dimen>
- <dimen name="autofill_dropdown_text_width">240dp</dimen>
+ <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+ <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+ <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+ <integer name="autofill_max_visible_datasets">3</integer>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77b..f98164b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
<string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
<!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
<string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+ <!-- Strings for dropdown presentation. -->
+ <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
+ <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
+ <string name="provider_icon_content_description">Credential provider icon</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 03ac605..985f322 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -30,6 +30,7 @@
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.provider.Settings
import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
@@ -48,7 +49,9 @@
import android.view.autofill.AutofillId
import android.widget.inline.InlinePresentationSpec
import android.credentials.CredentialManager
+import android.widget.RemoteViews
import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.core.content.ContextCompat
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -115,7 +118,7 @@
}
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId)
+ requestId)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
@@ -307,10 +310,14 @@
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
- var maxItemCount = totalEntryCount
- if (inlineMaxSuggestedCount > 0) {
- maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
- }
+ val maxDropdownDisplayLimit = this.resources.getInteger(
+ com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+ var maxInlineItemCount = totalEntryCount
+ maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+ val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+ Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+ (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
var i = 0
var datasetAdded = false
@@ -333,13 +340,8 @@
Log.e(TAG, "PendingIntent was missing from the entry.")
return@usernameLoop
}
- if (inlinePresentationSpecs == null) {
- Log.i(TAG, "Inline presentation spec is null, " +
- "building dropdown presentation only")
- }
- if (i >= maxItemCount) {
- Log.e(TAG, "Skipping because reached the max item count.")
- return@usernameLoop
+ if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+ return@usernameLoop;
}
val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
@@ -351,38 +353,26 @@
}
// Create inline presentation
var inlinePresentation: InlinePresentation? = null
- var spec: InlinePresentationSpec?
- if (inlinePresentationSpecs != null) {
- if (i < inlinePresentationSpecsCount) {
- spec = inlinePresentationSpecs[i]
+ if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+ val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+ inlinePresentationSpecs[i]
} else {
- spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
}
- val displayName: String = if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && primaryEntry.displayName != null) {
- primaryEntry.displayName!!
- } else {
- primaryEntry.userName
- }
- val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setTitle(displayName)
- sliceBuilder.setStartIcon(icon)
- if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
- == true) {
- sliceBuilder.setSubtitle(primaryEntry.userName)
- }
- inlinePresentation = InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ false)
+ inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+ spec!!, duplicateDisplayNamesForPasskeys)
}
- val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
- this, icon, primaryEntry)
- i++
+ var dropdownPresentation: RemoteViews? = null
+ if (i < lastDropdownDatasetIndex) {
+ dropdownPresentation = RemoteViewsFactory
+ .createDropdownPresentation(this, icon, primaryEntry)
+ }
val dataSetBuilder = Dataset.Builder()
val presentationBuilder = Presentations.Builder()
- .setMenuPresentation(dropdownPresentation)
+ if (dropdownPresentation != null) {
+ presentationBuilder.setMenuPresentation(dropdownPresentation)
+ }
if (inlinePresentation != null) {
presentationBuilder.setInlinePresentation(inlinePresentation)
}
@@ -398,6 +388,12 @@
.setAuthenticationExtras(fillInIntent.extras)
.build())
datasetAdded = true
+ i++
+
+ if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+ addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+ fillResponseBuilder)
+ }
}
val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
inlinePresentationSpecsCount)
@@ -408,6 +404,49 @@
return datasetAdded
}
+ private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+ InlinePresentation {
+ val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+ && primaryEntry.displayName != null) {
+ primaryEntry.displayName!!
+ } else {
+ primaryEntry.userName
+ }
+ val sliceBuilder = InlineSuggestionUi
+ .newContentBuilder(pendingIntent)
+ .setTitle(displayName)
+ sliceBuilder.setStartIcon(icon)
+ if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+ sliceBuilder.setSubtitle(primaryEntry.userName)
+ }
+ return InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ false)
+ }
+
+ private fun addDropdownMoreOptionsPresentation(
+ bottomSheetPendingIntent: PendingIntent,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder) {
+ val presentationBuilder = Presentations.Builder()
+ .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+ fillResponseBuilder.addDataset(
+ Dataset.Builder()
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build())
+ .build())
+ .setAuthentication(bottomSheetPendingIntent.intentSender)
+ .build()
+ )
+ }
+
private fun getLastInlinePresentationSpec(
inlinePresentationSpecs: List<InlinePresentationSpec>?,
inlinePresentationSpecsCount: Int
@@ -534,9 +573,9 @@
}
private fun getCredManRequest(
- structure: AssistStructure,
- sessionId: Int,
- requestId: Int
+ structure: AssistStructure,
+ sessionId: Int,
+ requestId: Int
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
traverseStructure(structure, credentialOptions)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index e039dea..68f1c86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -44,7 +44,7 @@
if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
return remoteViews
}
- setRemoteViewsPaddings(remoteViews, context)
+ setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
@@ -81,8 +81,46 @@
return remoteViews
}
+ fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+ var layoutId: Int = com.android.credentialmanager.R.layout
+ .credman_dropdown_bottom_sheet
+ val remoteViews = RemoteViews(context.packageName, layoutId)
+ setRemoteViewsPaddings(remoteViews, context)
+ remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+ com.android.credentialmanager
+ .R.string.dropdown_presentation_more_sign_in_options_text))
+
+ val textColorPrimary = ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.text_primary)
+ remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+ val icon = Icon.createWithResource(context, com
+ .android.credentialmanager.R.drawable.more_horiz_24px)
+ icon.setTint(ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.sign_in_options_icon_color))
+ remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+ remoteViews.setBoolean(
+ android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ remoteViews.setInt(
+ android.R.id.icon1,
+ setMaxHeightMethodName,
+ context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_icon_size));
+ val drawableId =
+ com.android.credentialmanager.R.drawable.more_options_list_item
+ remoteViews.setInt(
+ android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ return remoteViews
+ }
+
private fun setRemoteViewsPaddings(
remoteViews: RemoteViews, context: Context) {
+ val bottomPadding = context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+ setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+ }
+
+ private fun setRemoteViewsPaddings(
+ remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
val leftPadding = context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_view_left_padding)
val iconToTextPadding = context.resources.getDimensionPixelSize(
@@ -104,7 +142,7 @@
iconToTextPadding,
/* top=*/topPadding,
/* right=*/rightPadding,
- /* bottom=*/0)
+ primaryTextBottomPadding)
remoteViews.setViewPadding(
android.R.id.text2,
iconToTextPadding,
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c2cb757..bd56aae 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -16,6 +16,7 @@
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
"androidx.room_room-runtime",
+ "androidx.sqlite_sqlite",
"zxing-core",
"guava",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
index 84fea15..d92a863 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
@@ -35,8 +35,8 @@
/**
* Scope for the children of [MoreOptionsAction].
*/
-interface MoreOptionsScope {
- fun dismiss()
+abstract class MoreOptionsScope {
+ abstract fun dismiss()
@Composable
fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) {
@@ -60,7 +60,7 @@
val onDismiss = { expanded = false }
DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
val moreOptionsScope = remember(this) {
- object : MoreOptionsScope {
+ object : MoreOptionsScope() {
override fun dismiss() {
onDismiss()
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 983284c..2ccf323 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -130,7 +130,7 @@
}
private fun setContent(restrictions: Restrictions) {
- val fakeMoreOptionsScope = object : MoreOptionsScope {
+ val fakeMoreOptionsScope = object : MoreOptionsScope() {
override fun dismiss() {}
}
composeTestRule.setContent {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index cd5f597..b015b2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -234,6 +234,6 @@
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mSavedDrawable, mFileAuthority);
+ mSavedPhoto, mSavedDrawable, mFileAuthority, false);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index e83b9bc..b2de5a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -62,6 +62,7 @@
private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+ ".FULL_SCREEN_ACTIVITY";
+ static final String EXTRA_IS_USER_NEW = "is_user_new";
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
@@ -72,9 +73,13 @@
private Bitmap mNewUserPhotoBitmap;
private Drawable mNewUserPhotoDrawable;
private String mCachedDrawablePath;
-
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
+ this(activity, activityStarter, view, savedBitmap, savedDrawable, fileAuthority, true);
+ }
+ public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
+ ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority,
+ boolean isUserNew) {
mActivity = activity;
mActivityStarter = activityStarter;
mFileAuthority = fileAuthority;
@@ -82,7 +87,7 @@
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
mImageView = view;
- mImageView.setOnClickListener(v -> showAvatarPicker());
+ mImageView.setOnClickListener(v -> showAvatarPicker(isUserNew));
mNewUserPhotoBitmap = savedBitmap;
mNewUserPhotoDrawable = savedDrawable;
@@ -117,11 +122,12 @@
return mNewUserPhotoDrawable;
}
- private void showAvatarPicker() {
+ private void showAvatarPicker(boolean isUserNew) {
Intent intent;
if (Flags.avatarSync()) {
intent = new Intent(AVATAR_PICKER_ACTION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
} else {
intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 168e6e0..54ab5d1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -899,6 +899,14 @@
android:exported="true"
/>
+ <activity
+ android:name=".volume.panel.ui.activity.VolumePanelActivity"
+ android:label="@string/sound_settings"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:launchMode="singleInstance"
+ android:theme="@style/Theme.VolumePanelActivity" />
+
<activity android:name=".wallet.ui.WalletActivity"
android:label="@string/wallet_title"
android:theme="@style/Wallet.Theme"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2c35c77..7eca04a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -364,3 +364,10 @@
description: "Enables styled focus states on pin input field if keyboard is connected"
bug: "316106516"
}
+
+flag {
+ name: "enable_notif_linearlayout_optimized"
+ namespace: "systemui"
+ description: "Enables notification specific LinearLayout optimization"
+ bug: "316110233"
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index a7557d8..3c32594 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -34,6 +34,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -63,6 +64,14 @@
throwComposeUnavailableError()
}
+ override fun setVolumePanelActivityContent(
+ activity: ComponentActivity,
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ ) {
+ throwComposeUnavailableError()
+ }
+
override fun createFooterActionsView(
context: Context,
viewModel: FooterActionsViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
similarity index 77%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
index 22a74d2..c8dae76 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.panel.component.bottombar
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import dagger.Module
+
+@Module interface BottomBarModule
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index d63939d..afb860e 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -52,6 +52,8 @@
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.create
+import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -91,6 +93,19 @@
}
}
+ override fun setVolumePanelActivityContent(
+ activity: ComponentActivity,
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ ) {
+ activity.setContent {
+ VolumePanelRoot(
+ viewModel = viewModel,
+ onDismissAnimationFinished = onDismissAnimationFinished,
+ )
+ }
+ }
+
override fun createFooterActionsView(
context: Context,
viewModel: FooterActionsViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index c073b79b..a22fecf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -64,12 +64,11 @@
transitions = sceneTransitions,
)
- // Don't show hub mode UI if keyguard is not present. This is important since we're in the
- // shade, which can be opened from many locations.
- val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
+ // Don't show hub mode UI if communal is not available. Communal is only available if it has
+ // been enabled via settings and either keyguard is showing, or, the device is currently
+ // dreaming.
val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
-
- if (!isKeyguardShowing || !isCommunalAvailable) {
+ if (!isCommunalAvailable) {
return
}
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 409f15b..761e74e 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
@@ -97,11 +97,13 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
+import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -132,6 +134,8 @@
val removeButtonEnabled by remember {
derivedStateOf { selectedIndex.value != null || reorderingWidgets }
}
+ val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) =
+ remember { mutableStateOf(false) }
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -158,6 +162,11 @@
}
viewModel.setSelectedIndex(newIndex)
}
+ }
+ .thenIf(!viewModel.isEditMode) {
+ Modifier.pointerInput(Unit) {
+ detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) }
+ }
},
) {
CommunalHubLazyGrid(
@@ -207,6 +216,16 @@
PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
}
+ if (isButtonToEditWidgetsShowing) {
+ ButtonToEditWidgets(
+ onClick = {
+ setIsButtonToEditWidgetsShowing(false)
+ viewModel.onOpenWidgetEditor()
+ },
+ onHide = { setIsButtonToEditWidgetsShowing(false) },
+ )
+ }
+
// This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
// touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
// swipe back to the blank scene.
@@ -414,6 +433,34 @@
}
@Composable
+private fun ButtonToEditWidgets(
+ onClick: () -> Unit,
+ onHide: () -> Unit,
+) {
+ Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+ val colors = LocalAndroidColorScheme.current
+ Button(
+ modifier =
+ Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+ onClick = onClick,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.button_to_configure_widgets_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
Popup(
alignment = Alignment.TopCenter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index 1407494..bc1e429 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -20,9 +20,13 @@
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
import kotlinx.coroutines.coroutineScope
/**
@@ -44,6 +48,41 @@
}
}
+/**
+ * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an
+ * Offset representing the position relative to the containing element.
+ */
+suspend fun PointerInputScope.detectLongPressGesture(
+ pass: PointerEventPass = PointerEventPass.Initial,
+ onLongPress: ((Offset) -> Unit),
+) = coroutineScope {
+ awaitEachGesture {
+ val down = awaitFirstDown(pass = pass)
+ val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+ // wait for first tap up or long press
+ try {
+ withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) }
+ } catch (_: PointerEventTimeoutCancellationException) {
+ // withTimeout throws exception if timeout has passed before block completes
+ onLongPress.invoke(down.position)
+ consumeUntilUp(pass)
+ }
+ }
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes that
+ * something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp(
+ pass: PointerEventPass = PointerEventPass.Initial
+) {
+ do {
+ val event = awaitPointerEvent(pass = pass)
+ event.changes.fastForEach { it.consume() }
+ } while (event.changes.fastAny { it.pressed })
+}
+
/** Consume all gestures on the initial pass so that child elements do not receive them. */
suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
awaitEachGesture {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 56d6879..bf02d8a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -173,7 +173,8 @@
val belowLockIconPlaceable =
belowLockIconMeasurable.measure(
noMinConstraints.copy(
- maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
)
)
val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index fdf1166..616a7b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -16,19 +16,42 @@
package com.android.systemui.keyguard.ui.composable.blueprint
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
+import java.util.Optional
import javax.inject.Inject
/**
@@ -39,22 +62,174 @@
@Inject
constructor(
private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+ private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
+ private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
LockscreenLongPress(
viewModel = viewModel.longPress,
modifier = modifier,
- ) { _ ->
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Column(
+ modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = {
+ viewModel.getSmartSpacePaddingTop(resources)
+ }
+ ),
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ with(clockSection) { LargeClock() }
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ }
+ with(notificationSection) {
+ val splitShadeTopMargin: Dp =
+ if (Flags.centralizedStatusBarDimensRefactor()) {
+ largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
+ } else {
+ dimensionResource(
+ id = R.dimen.large_screen_shade_header_height
+ )
+ }
+ Notifications(
+ modifier =
+ Modifier.fillMaxHeight()
+ .weight(weight = 1f)
+ .padding(top = splitShadeTopMargin)
+ )
+ }
+ }
+
+ if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f40b871..8f21879 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,7 +16,8 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.layout.fillMaxWidth
+import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -75,7 +76,13 @@
) {
content {
AndroidView(
- factory = { checkNotNull(currentClock).smallClock.view },
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
modifier =
Modifier.padding(
horizontal =
@@ -83,6 +90,12 @@
)
.padding(top = { viewModel.getSmallClockTopMargin(view.context) })
.onTopPlacementChanged(onTopChanged),
+ update = {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
)
}
}
@@ -116,8 +129,19 @@
) {
content {
AndroidView(
- factory = { checkNotNull(currentClock).largeClock.view },
- modifier = Modifier.fillMaxWidth()
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
new file mode 100644
index 0000000..43d5453
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar
+
+import com.android.systemui.volume.panel.component.bottombar.ui.BottomBarComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.AlwaysAvailableCriteria
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface BottomBarModule {
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.BOTTOM_BAR)
+ fun bindMediaVolumeSliderComponent(component: BottomBarComponent): VolumePanelUiComponent
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.BOTTOM_BAR)
+ fun bindComponentAvailabilityCriteria(
+ defaultCriteria: AlwaysAvailableCriteria
+ ): ComponentAvailabilityCriteria
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
new file mode 100644
index 0000000..03c07f7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.panel.component.bottombar.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.BottomBarViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+@VolumePanelScope
+class BottomBarComponent
+@Inject
+constructor(
+ private val viewModel: BottomBarViewModel,
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ Row(
+ modifier = modifier.height(48.dp).fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ PlatformOutlinedButton(onClick = viewModel::onSettingsClicked) {
+ Text(text = stringResource(R.string.volume_panel_dialog_settings_button))
+ }
+ PlatformButton(onClick = viewModel::onDoneClicked) {
+ Text(text = stringResource(R.string.inline_done_button))
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt
new file mode 100644
index 0000000..e1834ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.volume.panel.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+
+/**
+ * Compose implementation of [VolumePanelUiComponent]. Each new UI component should implement this
+ * interface.
+ */
+interface ComposeVolumePanelUiComponent : VolumePanelUiComponent {
+
+ @Composable fun VolumePanelComposeScope.Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
new file mode 100644
index 0000000..dcd22fe
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.volume.panel.ui.composable
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+
+@Composable
+fun VolumePanelComposeScope.VerticalVolumePanelContent(
+ components: List<ComponentState>,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ ) {
+ for (component in components) {
+ AnimatedVisibility(component.isVisible) {
+ with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
new file mode 100644
index 0000000..c70c6b1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import android.content.res.Configuration.Orientation
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+
+class VolumePanelComposeScope(private val state: VolumePanelState) {
+
+ /**
+ * Layout orientation of the panel. It doesn't necessarily aligns with the device orientation,
+ * because in some cases we want to show bigger version of a portrait orientation when the
+ * device is in landscape.
+ */
+ @Orientation
+ val orientation: Int
+ get() = state.orientation
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
new file mode 100644
index 0000000..3487184
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.volume.panel.ui.composable
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+
+@Composable
+fun VolumePanelRoot(
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ PlatformTheme(isSystemInDarkTheme()) {
+ val state: VolumePanelState by viewModel.volumePanelState.collectAsState()
+ val components by viewModel.componentsLayout.collectAsState(null)
+
+ val transitionState =
+ remember { MutableTransitionState(false) }.apply { targetState = state.isVisible }
+
+ LaunchedEffect(transitionState.targetState, transitionState.isIdle) {
+ if (!transitionState.targetState && transitionState.isIdle) {
+ onDismissAnimationFinished()
+ }
+ }
+
+ Column(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .statusBarsPadding()
+ .clickable(onClick = { viewModel.dismissPanel() }),
+ verticalArrangement = Arrangement.Bottom,
+ ) {
+ AnimatedVisibility(
+ visibleState = transitionState,
+ enter = slideInVertically { it },
+ exit = slideOutVertically { it },
+ ) {
+ val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
+ Surface(
+ shape = RoundedCornerShape(topStart = radius, topEnd = radius),
+ color = MaterialTheme.colorScheme.surfaceBright,
+ ) {
+ Column {
+ components?.let { componentsState ->
+ with(VolumePanelComposeScope(state)) { Components(componentsState) }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun VolumePanelComposeScope.Components(state: ComponentsLayout) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ VerticalVolumePanelContent(
+ components = state.contentComponents,
+ modifier = Modifier.padding(dimensionResource(R.dimen.volume_panel_content_padding)),
+ )
+ } else {
+ TODO("Add landscape layout")
+ }
+
+ val horizontalPadding = dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding)
+ if (state.bottomBarComponent.isVisible) {
+ with(state.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
+ Content(
+ Modifier.navigationBarsPadding()
+ .padding(
+ start = horizontalPadding,
+ end = horizontalPadding,
+ bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding),
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 8d6d052..a862112 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -37,6 +37,8 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -112,6 +114,7 @@
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
+ kosmos.biometricSettingsRepository,
kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
@@ -420,6 +423,7 @@
@Test
fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() =
testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
setupFingerprint(FingerprintSensorType.POWER_BUTTON)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 81d5344..bd9ca30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,24 +16,28 @@
package com.android.systemui.communal.data.repository
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -44,19 +48,23 @@
class CommunalRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: CommunalRepositoryImpl
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
- private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
- private lateinit var sceneContainerRepository: SceneContainerRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneContainerRepository = kosmos.sceneContainerRepository
@Before
fun setUp() {
- val kosmos = testKosmos()
- sceneContainerRepository = kosmos.sceneContainerRepository
- featureFlagsClassic = FakeFeatureFlagsClassic()
+ secureSettings = FakeSettings()
+ userRepository = kosmos.fakeUserRepository
- featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ val listOfUserInfo = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(listOfUserInfo)
+
+ kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
underTest = createRepositoryImpl(false)
}
@@ -64,9 +72,13 @@
private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
return CommunalRepositoryImpl(
testScope.backgroundScope,
- featureFlagsClassic,
- FakeSceneContainerFlags(enabled = sceneContainerEnabled),
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ kosmos.fakeFeatureFlagsClassic,
+ kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
sceneContainerRepository,
+ kosmos.fakeUserRepository,
+ secureSettings,
)
}
@@ -147,4 +159,29 @@
assertThat(transitionState)
.isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
}
+
+ @Test
+ fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
+
+ val communalEnabled by collectLastValue(underTest.communalEnabledState)
+ assertThat(communalEnabled).isFalse()
+ }
+
+ @Test
+ fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
+
+ val communalEnabled by collectLastValue(underTest.communalEnabledState)
+ assertThat(communalEnabled).isTrue()
+ }
+
+ companion object {
+ private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 86279ef..1b7117f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -124,6 +124,7 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
+ keyguardRepository.setKeyguardShowing(true)
runCurrent()
assertThat(isAvailable).isTrue()
@@ -150,12 +151,27 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(secondaryUser)
+ keyguardRepository.setKeyguardShowing(true)
runCurrent()
assertThat(isAvailable).isFalse()
}
@Test
+ fun isCommunalAvailable_whenDreaming_true() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(mainUser)
+ keyguardRepository.setDreaming(true)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+
+ @Test
fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
testScope.runTest {
collectLastValue(underTest.isCommunalAvailable)
@@ -163,6 +179,7 @@
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
+ keyguardRepository.setKeyguardShowing(true)
runCurrent()
assertThat(widgetRepository.isHostActive()).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
new file mode 100644
index 0000000..74c1970
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CommunalTouchHandlerTest extends SysuiTestCase {
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+ @Mock
+ DreamTouchHandler.TouchSession mTouchSession;
+ CommunalTouchHandler mTouchHandler;
+
+ private static final int INITIATION_WIDTH = 20;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchHandler = new CommunalTouchHandler(
+ Optional.of(mCentralSurfaces),
+ mNotificationShadeWindowController,
+ INITIATION_WIDTH);
+ }
+
+ @Test
+ public void testSessionStartForcesShadeOpen() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
+ }
+
+ @Test
+ public void testEventPropagation() {
+ final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<InputChannelCompat.InputEventListener>
+ inputEventListenerArgumentCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+ inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+ verify(mCentralSurfaces).handleDreamTouch(motionEvent);
+ }
+
+ @Test
+ public void testTouchPilferingOnScroll() {
+ final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+ final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+ assertThat(gestureListenerArgumentCaptor.getValue()
+ .onScroll(motionEvent1, motionEvent2, 1, 1))
+ .isTrue();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
new file mode 100644
index 0000000..2fe4ef78
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.os.PowerManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
+import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.dozeServiceHost
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class SideFpsProgressBarViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private lateinit var underTest: SideFpsProgressBarViewModel
+ private val testScope = kosmos.testScope
+ private lateinit var mTestableLooper: TestableLooper
+
+ @Before
+ fun setup() {
+ mTestableLooper = TestableLooper.get(this)
+ allowTestableLooperAsMainThread()
+ }
+
+ private suspend fun setupRestToUnlockEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK)
+ overrideResource(R.bool.config_restToUnlockSupported, true)
+ kosmos.fakeFingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.POWER_BUTTON,
+ mutableMapOf(Pair("sensor", mock()))
+ )
+ kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.0f,
+ transitionState = TransitionState.STARTED
+ )
+ )
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1.0f,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ }
+
+ @Test
+ fun whenConfigDisabled_featureIsDisabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_restToUnlockSupported, false)
+ underTest = createViewModel()
+ val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+ assertThat(enabled).isFalse()
+ }
+
+ @Test
+ fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_restToUnlockSupported, true)
+ underTest = createViewModel()
+ val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+ assertThat(enabled).isFalse()
+ kosmos.fakeFingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.POWER_BUTTON,
+ mutableMapOf(Pair("sensor", mock()))
+ )
+ assertThat(enabled).isFalse()
+
+ kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+
+ runCurrent()
+ assertThat(enabled).isTrue()
+ }
+
+ @Test
+ fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() =
+ testScope.runTest {
+ setupRestToUnlockEnabled()
+ underTest = createViewModel()
+
+ kosmos.fakeKeyguardRepository.setIsDozing(false)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+
+ runCurrent()
+
+ assertThat(kosmos.fakePowerRepository.lastWakeReason)
+ .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC)
+ }
+
+ @Test
+ fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() =
+ testScope.runTest {
+ setupRestToUnlockEnabled()
+ underTest = createViewModel()
+
+ kosmos.fakeKeyguardRepository.setIsDozing(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+
+ runCurrent()
+
+ verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted()
+ }
+
+ private fun createViewModel() =
+ SideFpsProgressBarViewModel(
+ kosmos.applicationContext,
+ kosmos.deviceEntryFingerprintAuthInteractor,
+ kosmos.sideFpsSensorInteractor,
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
+ kosmos.displayStateInteractor,
+ kosmos.testDispatcher,
+ kosmos.applicationCoroutineScope,
+ kosmos.powerInteractor,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index f23716c..d5e43f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -25,10 +25,13 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -44,7 +47,6 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -56,7 +58,8 @@
@RunWith(AndroidJUnit4::class)
class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
private val testDispatcher = StandardTestDispatcher()
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
@@ -79,6 +82,8 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
new file mode 100644
index 0000000..51b8342
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeControllerSceneImplTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+
+ private lateinit var shadeInteractor: ShadeInteractor
+ private lateinit var underTest: ShadeControllerSceneImpl
+
+ @Before
+ fun setup() {
+ kosmos.testCase = this
+ kosmos.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeFeatureFlagsClassic.apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.NSSL_DEBUG_LINES, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ }
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ testScope.runCurrent()
+ shadeInteractor = kosmos.shadeInteractor
+ underTest = kosmos.shadeControllerSceneImpl
+ }
+
+ @Test
+ fun animateCollapseShade_noForceNoExpansion() =
+ testScope.runTest {
+ // GIVEN shade is collapsed and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setDeviceEntered(true)
+ setCollapsed()
+ underTest.addPostCollapseAction(testRunnable)
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade(0, force = false, delayed = false, 1f)
+ runCurrent()
+
+ // THEN the shade remains collapsed and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ verify(testRunnable, times(1)).run()
+ }
+
+ @Test
+ fun animateCollapseShade_expandedExcludeFlagOn() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ underTest.addPostCollapseAction(testRunnable)
+ setDeviceEntered(true)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested with FLAG_EXCLUDE_NOTIFICATION_PANEL
+ underTest.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL)
+ runCurrent()
+
+ // THEN the shade remains expanded and the post-collapse action did not run
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+ verify(testRunnable, never()).run()
+ }
+
+ @Test
+ fun animateCollapseShade_locked() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded on lockscreen
+ setDeviceEntered(false)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade()
+ runCurrent()
+
+ // THEN the shade collapses back to lockscreen and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun animateCollapseShade_unlocked() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded on an unlocked device
+ setDeviceEntered(true)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade()
+ runCurrent()
+
+ // THEN the shade collapses back to lockscreen and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun onCollapseShade_runPostCollapseActionsCalled() =
+ testScope.runTest {
+ // GIVEN shade is expanded and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setShadeFullyExpanded()
+ underTest.addPostCollapseAction(testRunnable)
+
+ // WHEN shade collapses
+ setCollapsed()
+
+ // THEN post-collapse action ran
+ verify(testRunnable, times(1)).run()
+ }
+
+ @Test
+ fun postOnShadeExpanded() =
+ testScope.runTest {
+ // GIVEN shade is collapsed and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setCollapsed()
+ underTest.postOnShadeExpanded(testRunnable)
+
+ // WHEN shade expands
+ setShadeFullyExpanded()
+
+ // THEN post-collapse action ran
+ verify(testRunnable, times(1)).run()
+ }
+
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+
+ private fun setDeviceEntered(isEntered: Boolean) {
+ setScene(
+ if (isEntered) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ )
+ assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+ }
+
+ private fun setCollapsed() {
+ setScene(SceneKey.Gone)
+ assertThat(shadeInteractor.isAnyExpanded.value).isFalse()
+ }
+
+ private fun setShadeFullyExpanded() {
+ setScene(SceneKey.Shade)
+ assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0a10b2c..0c7ce97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,11 +16,10 @@
package com.android.systemui.statusbar.notification.collection.render
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -34,18 +33,19 @@
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@SmallTest
+@RunWith(AndroidJUnit4::class)
class GroupExpansionManagerTest : SysuiTestCase() {
- private lateinit var gem: GroupExpansionManagerImpl
+ private lateinit var underTest: GroupExpansionManagerImpl
private val dumpManager: DumpManager = mock()
private val groupMembershipManager: GroupMembershipManager = mock()
- private val featureFlags = FakeFeatureFlagsClassic()
private val pipeline: NotifPipeline = mock()
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -85,79 +85,57 @@
whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
- gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
+ underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
}
@Test
- fun testNotifyOnlyOnChange_enabled() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun notifyOnlyOnChange() {
var listenerCalledCount = 0
- gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+ underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(summary1, false)
+ underTest.setGroupExpanded(summary1, false)
assertThat(listenerCalledCount).isEqualTo(0)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(1)
- gem.setGroupExpanded(summary2, true)
+ underTest.setGroupExpanded(summary2, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, false)
+ underTest.setGroupExpanded(summary2, false)
assertThat(listenerCalledCount).isEqualTo(3)
}
@Test
- fun testNotifyOnlyOnChange_disabled() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- var listenerCalledCount = 0
- gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
-
- gem.setGroupExpanded(summary1, false)
- assertThat(listenerCalledCount).isEqualTo(1)
- gem.setGroupExpanded(summary1, true)
- assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, true)
- assertThat(listenerCalledCount).isEqualTo(3)
- gem.setGroupExpanded(summary1, true)
- assertThat(listenerCalledCount).isEqualTo(4)
- gem.setGroupExpanded(summary2, false)
- assertThat(listenerCalledCount).isEqualTo(5)
- }
-
- @Test
- fun testExpandUnattachedEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun expandUnattachedEntry() {
// First, expand the entry when it is attached.
- gem.setGroupExpanded(summary1, true)
- assertThat(gem.isGroupExpanded(summary1)).isTrue()
+ underTest.setGroupExpanded(summary1, true)
+ assertThat(underTest.isGroupExpanded(summary1)).isTrue()
// Un-attach it, and un-expand it.
NotificationEntryBuilder.setNewParent(summary1, null)
- gem.setGroupExpanded(summary1, false)
+ underTest.setGroupExpanded(summary1, false)
// Expanding again should throw.
- assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.setGroupExpanded(summary1, true)
+ }
}
@Test
- fun testSyncWithPipeline() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- gem.attach(pipeline)
+ fun syncWithPipeline() {
+ underTest.attach(pipeline)
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
val listener: OnGroupExpansionChangeListener = mock()
- gem.registerGroupExpansionChangeListener(listener)
+ underTest.registerGroupExpansionChangeListener(listener)
beforeRenderListListener.onBeforeRenderList(entries)
verify(listener, never()).onGroupExpansionChange(any(), any())
// Expand one of the groups.
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
verify(listener).onGroupExpansionChange(summary1.row, true)
// Empty the pipeline list and verify that the group is no longer expanded.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
new file mode 100644
index 0000000..2cbcc5a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.collection.render
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupMembershipManagerTest : SysuiTestCase() {
+ private var underTest = GroupMembershipManagerImpl()
+
+ @Test
+ fun isChildInGroup_topLevel() {
+ val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
+ }
+
+ @Test
+ fun isChildInGroup_noParent() {
+ val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+ assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
+ }
+
+ @Test
+ fun isChildInGroup_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isChildInGroup(summary)).isFalse()
+ }
+
+ @Test
+ fun isGroupSummary_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
+ }
+
+ @Test
+ fun isGroupSummary_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isGroupSummary(summary)).isTrue()
+ }
+
+ @Test
+ fun isGroupSummary_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
+ }
+
+ @Test
+ fun getGroupSummary_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.getGroupSummary(entry)).isNull()
+ }
+
+ @Test
+ fun getGroupSummary_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
+ }
+
+ @Test
+ fun getGroupSummary_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 00a86ff..cc4ebd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -1,15 +1,17 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * 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.
+ * 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.phone
@@ -18,9 +20,9 @@
import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
-import android.testing.AndroidTestingRunner
import android.view.View
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
@@ -66,7 +68,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class ActivityStarterImplTest : SysuiTestCase() {
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var assistManager: AssistManager
@@ -139,7 +141,7 @@
}
@Test
- fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
val pendingIntent = mock(PendingIntent::class.java)
val parent = FrameLayout(context)
val view =
@@ -214,7 +216,7 @@
mainExecutor.runAllReady()
verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController).runPostCollapseRunnables()
+ verify(shadeController).collapseShadeForActivityStart()
}
@Test
@@ -226,7 +228,7 @@
mainExecutor.runAllReady()
verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController, never()).runPostCollapseRunnables()
+ verify(shadeController, never()).collapseShadeForActivityStart()
}
@Test
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 16eba22..1365a11 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/internet_connectivity_dialog"
android:layout_width="@dimen/large_dialog_width"
@@ -386,9 +387,8 @@
</LinearLayout>
</LinearLayout>
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/button_layout"
- android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -398,53 +398,58 @@
android:clickable="false"
android:focusable="false">
- <LinearLayout
+ <Button
+ android:id="@+id/apm_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="start|center_vertical"
- android:orientation="horizontal">
- <Button
- android:id="@+id/apm_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/turn_off_airplane_mode"
- android:ellipsize="end"
- android:maxLines="1"
- style="@style/Widget.Dialog.Button.BorderButton"
- android:clickable="true"
- android:focusable="true"/>
+ android:layout_marginEnd="10dp"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/turn_off_airplane_mode"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/share_wifi_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
- <Button
- android:id="@+id/share_wifi_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/share_wifi_button_text"
- style="?android:attr/buttonBarNeutralButtonStyle"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"
- android:visibility="gone"/>
- </LinearLayout>
-
- <LinearLayout
+ <Button
+ android:id="@+id/share_wifi_button"
+ style="?android:attr/buttonBarNeutralButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_gravity="end|center_vertical">
- <Button
- android:id="@+id/done_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/inline_done_button"
- style="@style/Widget.Dialog.Button"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"/>
- </LinearLayout>
- </LinearLayout>
+ android:layout_marginEnd="10dp"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:visibility="gone"
+ app:layout_constraintHorizontal_bias="0"
+ android:text="@string/share_wifi_button_text"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/done_button"
+ app:layout_constraintStart_toEndOf="@id/apm_button"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Button
+ android:id="@+id/done_button"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/inline_done_button"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 2c7467d..fab7840 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -27,7 +27,7 @@
tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
>
- <LinearLayout
+ <com.android.systemui.animation.view.LaunchableLinearLayout
android:id="@+id/icons_container"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index dca84b9..b792acc 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -27,10 +27,11 @@
android:fitsSystemWindows="true">
<!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
- <ViewStub
+ <View
android:id="@+id/communal_ui_stub"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ android:visibility="gone" />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_behind"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 09b02f8..4209c1f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1805,6 +1805,9 @@
<dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
<dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
+ <!-- The width of the swipe target to initiate opening communal hub over dreams. -->
+ <dimen name="communal_gesture_initiation_width">48dp</dimen>
+
<!-- The position of the end guide, which dream overlay complications can align their start with
if their end is aligned with the parent end. Represented as the percentage over from the
start of the parent container. -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index ec4c7d5..8ec5ccd 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -227,6 +227,7 @@
<item type="id" name="ambient_indication_container" />
<item type="id" name="status_view_media_container" />
<item type="id" name="smart_space_barrier_bottom" />
+ <item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
<!-- Privacy dialog -->
<item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9bc7681..2b43360 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1089,6 +1089,8 @@
<string name="cta_label_to_open_widget_picker">Add more widgets</string>
<!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
+ <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
+ <string name="button_to_configure_widgets_text">Customize widgets</string>
<!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
<string name="edit_widget">Edit widget</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
index 259cca8..9e92c93 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
@@ -16,8 +16,11 @@
package com.android.systemui.shared.system;
-import android.graphics.Matrix;
+import static android.os.Trace.TRACE_TAG_INPUT;
+
import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -52,23 +55,24 @@
return target.addBatch(src);
}
- /** @see MotionEvent#createRotateMatrix */
- public static Matrix createRotationMatrix(
- /*@Surface.Rotation*/ int rotation, int displayW, int displayH) {
- return MotionEvent.createRotateMatrix(rotation, displayW, displayH);
- }
-
/**
* @see BatchedInputEventReceiver
*/
public static class InputEventReceiver {
+ private final String mName;
private final BatchedInputEventReceiver mReceiver;
+ @Deprecated
public InputEventReceiver(InputChannel inputChannel, Looper looper,
Choreographer choreographer, final InputEventListener listener) {
- mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
+ this("unknown", inputChannel, looper, choreographer, listener);
+ }
+ public InputEventReceiver(String name, InputChannel inputChannel, Looper looper,
+ Choreographer choreographer, final InputEventListener listener) {
+ mName = name;
+ mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
@Override
public void onInputEvent(InputEvent event) {
listener.onInputEvent(event);
@@ -89,6 +93,9 @@
*/
public void dispose() {
mReceiver.dispose();
+ Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed");
+ Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName
+ + ") disposed");
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
index c4aac11..78beaf7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
@@ -17,8 +17,13 @@
import android.hardware.input.InputManagerGlobal;
import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
import android.view.Choreographer;
import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -27,14 +32,20 @@
* @see android.view.InputMonitor
*/
public class InputMonitorCompat {
+ static final String TAG = "InputMonitorCompat";
private final InputMonitor mInputMonitor;
+ private final String mName;
/**
* Monitor input on the specified display for gestures.
*/
- public InputMonitorCompat(String name, int displayId) {
+ public InputMonitorCompat(@NonNull String name, int displayId) {
+ mName = name + "-disp" + displayId;
mInputMonitor = InputManagerGlobal.getInstance()
.monitorGestureInput(name, displayId);
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created");
+ Log.d(TAG, "Input monitor (" + mName + ") created");
+
}
/**
@@ -45,10 +56,19 @@
}
/**
+ * @see InputMonitor#getSurface()
+ */
+ public SurfaceControl getSurface() {
+ return mInputMonitor.getSurface();
+ }
+
+ /**
* @see InputMonitor#dispose()
*/
public void dispose() {
mInputMonitor.dispose();
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed");
+ Log.d(TAG, "Input monitor (" + mName + ") disposed");
}
/**
@@ -56,7 +76,9 @@
*/
public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
InputEventListener listener) {
- return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created");
+ Log.d(TAG, "Input event receiver for monitor (" + mName + ") created");
+ return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer,
listener);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3e8c6a7..536f3af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,7 +114,6 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
-import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -383,7 +382,6 @@
private List<SubscriptionInfo> mSubscriptionInfo;
@VisibleForTesting
protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- private boolean mFingerprintDetectRunning;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1005,7 +1003,6 @@
final boolean wasCancellingRestarting = mFingerprintRunningState
== BIOMETRIC_STATE_CANCELLING_RESTARTING;
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- mFingerprintDetectRunning = false;
if (wasCancellingRestarting) {
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else {
@@ -1114,9 +1111,6 @@
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
- if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
- mFingerprintDetectRunning = false;
- }
mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -2105,7 +2099,6 @@
@VisibleForTesting
void resetBiometricListeningState() {
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- mFingerprintDetectRunning = false;
}
@VisibleForTesting
@@ -2544,10 +2537,8 @@
return;
}
final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
- final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
- final boolean runningOrRestarting = running
+ final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
- final boolean runDetect = shouldRunFingerprintDetect();
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2559,24 +2550,10 @@
mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
- startListeningForFingerprint(runDetect);
- } else if (running && runDetect && !mFingerprintDetectRunning) {
- if (action == BIOMETRIC_ACTION_STOP) {
- mLogger.v("Ignoring startListeningForFingerprint(detect)");
- return;
- }
- // stop running authentication and start running fingerprint detection
- stopListeningForFingerprint();
- startListeningForFingerprint(true);
+ startListeningForFingerprint();
}
}
- private boolean shouldRunFingerprintDetect() {
- return !isUnlockingWithFingerprintAllowed()
- || (Flags.runFingerprintDetectOnDismissibleKeyguard()
- && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
- }
-
/**
* If a user is encrypted or not.
* This is NOT related to the lock screen being visible or not.
@@ -2832,6 +2809,7 @@
&& biometricEnabledForUser
&& !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+ final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
!strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
@@ -2894,7 +2872,7 @@
}
}
- private void startListeningForFingerprint(boolean runDetect) {
+ private void startListeningForFingerprint() {
final int userId = mSelectedUserInteractor.getSelectedUserId();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
@@ -2924,20 +2902,18 @@
mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
}
- if (runDetect) {
+ if (!isUnlockingWithFingerprintAllowed()) {
mLogger.v("startListeningForFingerprint - detect");
mFpm.detectFingerprint(
mFingerprintCancelSignal,
mFingerprintDetectionCallback,
fingerprintAuthenticateOptions);
- mFingerprintDetectRunning = true;
} else {
mLogger.v("startListeningForFingerprint");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback,
null /* handler */,
fingerprintAuthenticateOptions);
- mFingerprintDetectRunning = false;
}
setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
}
@@ -3962,7 +3938,6 @@
mSelectedUserInteractor.getSelectedUserId()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
- pw.println(" mFingerprintDetectRunning=" + mFingerprintDetectRunning);
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0f5f869..4372826 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -20,11 +20,12 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.UserInfo;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.annotation.NonNull;
-import com.android.systemui.res.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
@@ -32,6 +33,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -61,6 +63,7 @@
private final SecureSettings mSecureSettings;
private final ResetSessionDialogFactory mResetSessionDialogFactory;
private final GuestSessionNotification mGuestSessionNotification;
+ private final HandlerThread mHandlerThread;
@VisibleForTesting
public final UserTracker.Callback mUserChangedCallback =
@@ -111,13 +114,16 @@
mSecureSettings = secureSettings;
mGuestSessionNotification = guestSessionNotification;
mResetSessionDialogFactory = resetSessionDialogFactory;
+ mHandlerThread = new HandlerThread("GuestResumeSessionReceiver");
+ mHandlerThread.start();
}
/**
* Register this receiver with the {@link BroadcastDispatcher}
*/
public void register() {
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
}
private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 86f372a..71d0e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -35,7 +35,6 @@
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -51,7 +50,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -64,7 +62,6 @@
*
* The ripple uses the accent color of the current theme.
*/
-@ExperimentalCoroutinesApi
@SysUISingleton
class AuthRippleController @Inject constructor(
private val sysuiContext: Context,
@@ -316,18 +313,6 @@
mView.fadeDwellRipple()
}
}
-
- override fun onBiometricDetected(
- userId: Int,
- biometricSourceType: BiometricSourceType,
- isStrongBiometric: Boolean
- ) {
- // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
- if (!DeviceEntryUdfpsRefactor.isEnabled &&
- keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
- showUnlockRipple(biometricSourceType)
- }
- }
}
private val configurationChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ad2136a..d28dbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -94,6 +94,10 @@
override fun onAuthenticationStopped() {
updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
}
+
+ override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
+
+ override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
}
updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index 348b54e..c3dc2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -26,20 +26,24 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.res.R
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsSensorInteractor
@Inject
@@ -49,6 +53,7 @@
windowManager: WindowManager,
displayStateInteractor: DisplayStateInteractor,
fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
+ biometricSettingsRepository: BiometricSettingsRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: SideFpsLogger,
) {
@@ -84,13 +89,24 @@
.map { it ?: 0L }
.onEach { logger.authDurationChanged(it) }
+ private val isSettingEnabled: Flow<Boolean> =
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled
+ .flatMapLatest { enabledAndEnrolled ->
+ if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) {
+ flowOf(false)
+ } else {
+ fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+ }
+ }
+ .onEach { logger.restToUnlockSettingEnabledChanged(it) }
+
val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
- if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
+ if (!isProlongedTouchEnabledForDevice) {
flowOf(false)
} else {
combine(
isAvailable,
- fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+ isSettingEnabled,
) { sfpsAvailable, isSettingEnabled ->
sfpsAvailable && isSettingEnabled
}
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 dca0338..e78a7a9 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
@@ -21,6 +21,7 @@
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.util.Log
import android.view.HapticFeedbackConstants
@@ -240,7 +241,7 @@
promptSelectorInteractor.prompt
.map {
when {
- it == null -> null
+ !customBiometricPrompt() || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
else -> context.packageManager.getApplicationIcon(it.opPackageName)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be40..addd880 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -20,13 +20,18 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -34,16 +39,26 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
+ /**
+ * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
+ * settings).
+ */
+ val communalEnabledState: StateFlow<Boolean>
+
/** Whether the communal hub is showing. */
val isCommunalHubShowing: Flow<Boolean>
@@ -72,13 +87,36 @@
class CommunalRepositoryImpl
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Background backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val featureFlagsClassic: FeatureFlagsClassic,
sceneContainerFlags: SceneContainerFlags,
sceneContainerRepository: SceneContainerRepository,
+ userRepository: UserRepository,
+ private val secureSettings: SecureSettings
) : CommunalRepository {
+
+ private val communalEnabledSettingState: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
+ .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+ override val communalEnabledState: StateFlow<Boolean> =
+ if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
+ communalEnabledSettingState
+ .filterNotNull()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = true
+ )
+ } else {
+ MutableStateFlow(false)
+ }
+
override val isCommunalEnabled: Boolean
- get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ get() = communalEnabledState.value
private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT)
@@ -115,4 +153,26 @@
} else {
desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
}
+
+ private fun observeSettings(userId: Int): Flow<Boolean> =
+ secureSettings
+ .observerFlow(
+ userId = userId,
+ names =
+ arrayOf(
+ GLANCEABLE_HUB_ENABLED,
+ )
+ )
+ // Force an update
+ .onStart { emit(Unit) }
+ .map { readFromSettings(userId) }
+
+ private suspend fun readFromSettings(userId: Int): Boolean =
+ withContext(backgroundDispatcher) {
+ secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
+ }
+
+ companion object {
+ private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 44b0383..c36f7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -68,11 +68,14 @@
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
-
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
+ /** A {@link StateFlow} that tracks whether communal features are enabled. */
+ val communalEnabledState: StateFlow<Boolean>
+ get() = communalRepository.communalEnabledState
+
/** Whether communal features are enabled and available. */
val isCommunalAvailable: StateFlow<Boolean> =
flowOf(isCommunalEnabled)
@@ -81,8 +84,12 @@
combine(
keyguardInteractor.isEncryptedOrLockdown,
userRepository.selectedUserInfo,
- ) { isEncryptedOrLockdown, selectedUserInfo ->
- !isEncryptedOrLockdown && selectedUserInfo.isMain
+ keyguardInteractor.isKeyguardVisible,
+ keyguardInteractor.isDreaming,
+ ) { isEncryptedOrLockdown, selectedUserInfo, isKeyguardVisible, isDreaming ->
+ !isEncryptedOrLockdown &&
+ selectedUserInfo.isMain &&
+ (isKeyguardVisible || isDreaming)
}
else flowOf(false)
}
@@ -154,8 +161,6 @@
it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
}
- val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible
-
/** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 4e5be9b..309c84e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -27,12 +27,15 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Encapsulates business-logic related to communal tutorial state. */
@@ -48,7 +51,7 @@
communalInteractor: CommunalInteractor,
) {
/** An observable for whether the tutorial is available. */
- val isTutorialAvailable: Flow<Boolean> =
+ val isTutorialAvailable: StateFlow<Boolean> =
combine(
communalInteractor.isCommunalAvailable,
keyguardInteractor.isKeyguardVisible,
@@ -58,7 +61,11 @@
isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/**
* A flow of the new tutorial state after transitioning. The new state will be calculated based
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
index 4dfc371..0120b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
@@ -18,7 +18,6 @@
package com.android.systemui.communal.ui.binder
import android.widget.TextView
-import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -32,16 +31,14 @@
fun bind(
view: TextView,
viewModel: CommunalTutorialIndicatorViewModel,
+ isPreviewMode: Boolean = false,
): DisposableHandle {
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.showIndicator.collect { isVisible ->
- updateView(
- view = view,
- isIndicatorVisible = isVisible,
- )
+ viewModel.showIndicator(isPreviewMode).collect { showIndicator ->
+ view.isVisible = showIndicator
}
}
@@ -51,18 +48,4 @@
return disposableHandle
}
-
- private fun updateView(
- isIndicatorVisible: Boolean,
- view: TextView,
- ) {
- if (!isIndicatorVisible) {
- view.isGone = true
- return
- }
-
- if (!view.isVisible) {
- view.isVisible = true
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
index 027cc96..2d9dd50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -120,6 +120,7 @@
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM
)
+ setVisibility(tutorialIndicatorId, View.GONE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index acc7981..1e64d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -35,8 +35,6 @@
) {
val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
- val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
-
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
/** Whether widgets are currently being re-ordered. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
index 274e61a..63a4972 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
@@ -20,17 +20,30 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
/** View model for communal tutorial indicator on keyguard */
class CommunalTutorialIndicatorViewModel
@Inject
constructor(
- communalTutorialInteractor: CommunalTutorialInteractor,
+ private val communalTutorialInteractor: CommunalTutorialInteractor,
bottomAreaInteractor: KeyguardBottomAreaInteractor,
) {
- /** An observable for whether the tutorial indicator view should be visible. */
- val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable
+ /**
+ * An observable for whether the tutorial indicator view should be visible.
+ *
+ * @param isPreviewMode Whether for preview keyguard mode in wallpaper settings.
+ */
+ fun showIndicator(isPreviewMode: Boolean): StateFlow<Boolean> {
+ return if (isPreviewMode) {
+ MutableStateFlow(false).asStateFlow()
+ } else {
+ communalTutorialInteractor.isTutorialAvailable
+ }
+ }
/** An observable for the alpha level for the tutorial indicator. */
val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 2f9fd2e..947cb02 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -34,6 +34,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -73,6 +74,12 @@
onEditDone: () -> Unit,
)
+ fun setVolumePanelActivityContent(
+ activity: ComponentActivity,
+ viewModel: VolumePanelViewModel,
+ onDismissAnimationFinished: () -> Unit,
+ )
+
/** Create a [View] to represent [viewModel] on screen. */
fun createFooterActionsView(
context: Context,
@@ -89,7 +96,7 @@
sceneByKey: Map<SceneKey, Scene>,
): View
- /** Creates sticky key dialog presenting provided [viewModel] **/
+ /** Creates sticky key dialog presenting provided [viewModel] */
fun createStickyKeysDialog(
dialogFactory: SystemUIDialogFactory,
viewModel: StickyKeysIndicatorViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
new file mode 100644
index 0000000..c9b56a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
+public class CommunalTouchHandler implements DreamTouchHandler {
+ private final int mInitiationWidth;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final Optional<CentralSurfaces> mCentralSurfaces;
+
+ @Inject
+ public CommunalTouchHandler(
+ Optional<CentralSurfaces> centralSurfaces,
+ NotificationShadeWindowController notificationShadeWindowController,
+ @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
+ mInitiationWidth = initiationWidth;
+ mCentralSurfaces = centralSurfaces;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ }
+
+ @Override
+ public void onSessionStart(TouchSession session) {
+ mCentralSurfaces.ifPresent(surfaces -> handleSessionStart(surfaces, session));
+ }
+
+ @Override
+ public void getTouchInitiationRegion(Rect bounds, Region region) {
+ final Rect outBounds = new Rect(bounds);
+ outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
+ region.op(outBounds, Region.Op.UNION);
+ }
+
+ private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
+ // Force the notification shade window open (otherwise the hub won't show while swiping).
+ mNotificationShadeWindowController.setForcePluginOpen(true, this);
+
+ session.registerInputListener(ev -> {
+ surfaces.handleDreamTouch((MotionEvent) ev);
+ if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+ var unused = session.pop();
+ }
+ });
+
+ session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return true;
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
index 94fe4bd..0f08d37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
@@ -18,11 +18,13 @@
import android.content.res.Resources;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.CommunalTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.dreams.touch.ShadeTouchHandler;
+import com.android.systemui.res.R;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
@@ -33,7 +35,7 @@
* Dependencies for swipe down to notification over dream.
*/
@Module
-public class ShadeModule {
+public abstract class ShadeModule {
/**
* The height, defined in pixels, of the gesture initiation region at the top of the screen for
* swiping down notifications.
@@ -41,15 +43,22 @@
public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
"notification_shade_gesture_initiation_height";
+ /** Width of swipe gesture edge to show communal hub. */
+ public static final String COMMUNAL_GESTURE_INITIATION_WIDTH =
+ "communal_gesture_initiation_width";
+
/**
* Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
*/
- @Provides
+ @Binds
@IntoSet
- public static DreamTouchHandler providesNotificationShadeTouchHandler(
- ShadeTouchHandler touchHandler) {
- return touchHandler;
- }
+ public abstract DreamTouchHandler providesNotificationShadeTouchHandler(
+ ShadeTouchHandler touchHandler);
+
+ /** Provides {@link CommunalTouchHandler}. */
+ @Binds
+ @IntoSet
+ public abstract DreamTouchHandler bindCommunalTouchHandler(CommunalTouchHandler touchHandler);
/**
* Provides the height of the gesture area for notification swipe down.
@@ -59,4 +68,13 @@
public static int providesNotificationShadeGestureRegionHeight(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
}
+
+ /**
+ * Provides the width of the gesture area for swiping open communal hub.
+ */
+ @Provides
+ @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
+ public static int providesCommunalGestureInitiationWidth(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3f7c152..d2883cc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -44,11 +44,11 @@
// 100 - notification
// TODO(b/297792660): Tracking Bug
@JvmField val UNCLEARED_TRANSIENT_HUN_FIX =
- unreleasedFlag("uncleared_transient_hun_fix", teamfood = true)
+ releasedFlag("uncleared_transient_hun_fix")
// TODO(b/298308067): Tracking Bug
@JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
- unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true)
+ releasedFlag("swipe_uncleared_transient_view_fix")
// TODO(b/254512751): Tracking Bug
val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
@@ -102,12 +102,6 @@
default = true
)
- /** Only notify group expansion listeners when a change happens. */
- // TODO(b/292213543): Tracking Bug
- @JvmField
- val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- releasedFlag("notification_group_expansion_change")
-
// TODO(b/301955929)
@JvmField
val NOTIF_LS_BACKGROUND_THREAD =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3de9e68..a95ddb5 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2437,6 +2437,7 @@
return true;
}
});
+ mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 9d6b30f..9d38be9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -40,13 +40,13 @@
@Inject
constructor(
@Background private val scope: CoroutineScope,
+ @Main mainDispatcher: CoroutineDispatcher,
+ @Background bgDispatcher: CoroutineDispatcher,
private val glanceableHubTransitions: GlanceableHubTransitions,
private val keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
private val powerInteractor: PowerInteractor,
- @Main mainDispatcher: CoroutineDispatcher,
- @Background bgDispatcher: CoroutineDispatcher,
) :
TransitionInteractor(
fromState = KeyguardState.GLANCEABLE_HUB,
@@ -62,6 +62,8 @@
listenForHubToDozing()
listenForHubToPrimaryBouncer()
listenForHubToAlternateBouncer()
+ listenForHubToOccluded()
+ listenForHubToGone()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -129,6 +131,29 @@
}
}
+ private fun listenForHubToOccluded() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+ (isOccluded, keyguardState) ->
+ if (isOccluded && keyguardState == fromState) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
+ }
+
+ private fun listenForHubToGone() {
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { (isKeyguardGoingAway, lastStartedStep) ->
+ if (isKeyguardGoingAway && lastStartedStep.to == fromState) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
val DEFAULT_DURATION = 400.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 742790e..7477624 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -25,6 +26,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -45,6 +47,7 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
+ private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
@@ -56,18 +59,27 @@
override fun start() {
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
- listenForGoneToLockscreen()
+ listenForGoneToLockscreenOrHub()
listenForGoneToDreamingLockscreenHosted()
}
// Primarily for when the user chooses to lock down the device
- private fun listenForGoneToLockscreen() {
+ private fun listenForGoneToLockscreenOrHub() {
scope.launch {
keyguardInteractor.isKeyguardShowing
- .sample(startedKeyguardTransitionStep, ::Pair)
- .collect { (isKeyguardShowing, lastStartedStep) ->
+ .sample(
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 40061f4..efb604d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,12 +18,14 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -44,6 +46,7 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val powerInteractor: PowerInteractor,
+ private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.OCCLUDED,
@@ -53,7 +56,7 @@
) {
override fun start() {
- listenForOccludedToLockscreen()
+ listenForOccludedToLockscreenOrHub()
listenForOccludedToDreaming()
listenForOccludedToAodOrDozing()
listenForOccludedToGone()
@@ -86,18 +89,15 @@
}
}
- private fun listenForOccludedToLockscreen() {
+ private fun listenForOccludedToLockscreenOrHub() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
- combine(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
)
- .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
if (
@@ -105,7 +105,13 @@
isShowing &&
lastStartedKeyguardState.to == KeyguardState.OCCLUDED
) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 91747e0..36bd905 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -98,7 +98,7 @@
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
- val isDozing: Flow<Boolean> = repository.isDozing
+ val isDozing: StateFlow<Boolean> = repository.isDozing
/** Receive an event for doze time tick */
val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 3dd3e07..8d6493f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -53,7 +54,8 @@
}
// Apply transition.
- if (prevBluePrint != null && prevBluePrint != blueprint) {
+ if (!keyguardBottomAreaRefactor() && prevBluePrint != null &&
+ prevBluePrint != blueprint) {
TransitionManager.beginDelayedTransition(
constraintLayout,
BaseBlueprintTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 400b8bf..3c3ebdf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -89,6 +89,12 @@
}
}
}
+ launch {
+ if (!migrateClocksToBlueprint()) return@launch
+ viewModel.isAodIconsVisible.collect {
+ applyConstraints(clockSection, keyguardRootView, true)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index eb3afb7..841bad4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -39,6 +39,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
+import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
@@ -48,6 +49,8 @@
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -133,6 +136,7 @@
private val screenOffAnimationController: ScreenOffAnimationController,
private val shadeInteractor: ShadeInteractor,
private val secureSettings: SecureSettings,
+ private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -408,6 +412,8 @@
smartSpaceView?.let {
KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
}
+
+ setupCommunalTutorialIndicator(keyguardRootView)
}
)
}
@@ -601,6 +607,17 @@
}
}
+ private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) {
+ keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let {
+ indicatorView ->
+ CommunalTutorialIndicatorViewBinder.bind(
+ indicatorView,
+ communalTutorialViewModel,
+ isPreviewMode = true,
+ )
+ }
+ }
+
private suspend fun fetchThemeStyleFromSetting(): Style {
val overlayPackageJson =
withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index ed7abff..bc6c7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -57,7 +57,6 @@
private var nicBindingDisposable: DisposableHandle? = null
private val nicId = R.id.aod_notification_icon_container
private lateinit var nic: NotificationIconContainer
- private val smartSpaceBarrier = View.generateViewId()
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b344d3b..a1b3f27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.view.View
+import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -35,6 +36,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
@@ -61,6 +63,7 @@
protected val keyguardClockViewModel: KeyguardClockViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
+ val smartspaceViewModel: KeyguardSmartspaceViewModel,
val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
@@ -117,6 +120,35 @@
private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
+
+ fun constrainWeatherClockDateIconsBarrier(constraints: ConstraintSet) {
+ constraints.apply {
+ if (keyguardClockViewModel.isAodIconsVisible.value) {
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(sharedR.id.bc_smartspace_view, R.id.aod_notification_icon_container)
+ )
+ } else {
+ if (smartspaceViewModel.bcSmartspaceVisibility.value == VISIBLE) {
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ (sharedR.id.bc_smartspace_view)
+ )
+ } else {
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ getDimen(ENHANCED_SMARTSPACE_HEIGHT),
+ (R.id.lockscreen_clock_view)
+ )
+ }
+ }
+ }
+ }
open fun applyDefaultConstraints(constraints: ConstraintSet) {
val guideline =
if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
@@ -173,6 +205,8 @@
}
connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
}
+
+ constrainWeatherClockDateIconsBarrier(constraints)
}
private fun getDimen(name: String): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f37d9f8..6763e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
import javax.inject.Inject
@@ -44,6 +45,7 @@
private val keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
+ notifsKeyguardInteractor: NotificationsKeyguardInteractor,
) {
var burnInLayer: Layer? = null
val useLargeClock: Boolean
@@ -91,6 +93,13 @@
initialValue = false
)
+ val isAodIconsVisible: StateFlow<Boolean> =
+ notifsKeyguardInteractor.areNotificationsFullyHidden.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
+
// Needs to use a non application context to get display cutout.
fun getSmallClockTopMargin(context: Context) =
if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 693e3b7..ca9c857 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -20,7 +20,7 @@
import android.content.Context
import android.graphics.Point
import androidx.annotation.VisibleForTesting
-import androidx.core.animation.doOnEnd
+import androidx.core.animation.addListener
import com.android.systemui.Flags
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
@@ -30,15 +30,18 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.DozeServiceHost
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,13 +49,14 @@
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsProgressBarViewModel
@Inject
@@ -63,9 +67,11 @@
// todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
// DozeInteractor as DozeServiceHost already depends on DozeInteractor.
private val dozeServiceHost: DozeServiceHost,
+ private val keyguardInteractor: KeyguardInteractor,
displayStateInteractor: DisplayStateInteractor,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application private val applicationScope: CoroutineScope,
+ private val powerInteractor: PowerInteractor,
) {
private val _progress = MutableStateFlow(0.0f)
private val _visible = MutableStateFlow(false)
@@ -176,48 +182,54 @@
return@collectLatest
}
animatorJob =
- combine(
- sfpsSensorInteractor.authenticationDuration,
- fpAuthRepository.authenticationStatus,
- ::Pair
- )
- .onEach { (authDuration, authStatus) ->
- when (authStatus) {
- is AcquiredFingerprintAuthenticationStatus -> {
- if (authStatus.fingerprintCaptureStarted) {
- _visible.value = true
- dozeServiceHost.fireSideFpsAcquisitionStarted()
- _animator?.cancel()
- _animator =
- ValueAnimator.ofFloat(0.0f, 1.0f)
- .setDuration(authDuration)
- .apply {
- addUpdateListener {
- _progress.value = it.animatedValue as Float
- }
- addListener(
- doOnEnd {
- if (_progress.value == 0.0f) {
- _visible.value = false
- }
+ sfpsSensorInteractor.authenticationDuration
+ .flatMapLatest { authDuration ->
+ _animator?.cancel()
+ fpAuthRepository.authenticationStatus.map { authStatus ->
+ when (authStatus) {
+ is AcquiredFingerprintAuthenticationStatus -> {
+ if (authStatus.fingerprintCaptureStarted) {
+ if (keyguardInteractor.isDozing.value) {
+ dozeServiceHost.fireSideFpsAcquisitionStarted()
+ } else {
+ powerInteractor
+ .wakeUpForSideFingerprintAcquisition()
+ }
+ _animator?.cancel()
+ _animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f)
+ .setDuration(authDuration)
+ .apply {
+ addUpdateListener {
+ _progress.value =
+ it.animatedValue as Float
}
- )
- }
- _animator?.start()
- } else if (authStatus.fingerprintCaptureCompleted) {
- onFingerprintCaptureCompleted()
- } else {
- // Abandoned FP Auth attempt
- _animator?.reverse()
+ addListener(
+ onEnd = {
+ if (_progress.value == 0.0f) {
+ _visible.value = false
+ }
+ },
+ onStart = { _visible.value = true },
+ onCancel = { _visible.value = false }
+ )
+ }
+ _animator?.start()
+ } else if (authStatus.fingerprintCaptureCompleted) {
+ onFingerprintCaptureCompleted()
+ } else {
+ // Abandoned FP Auth attempt
+ _animator?.reverse()
+ }
}
+ is ErrorFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is FailFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is SuccessFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ else -> Unit
}
- is ErrorFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- is FailFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- is SuccessFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- else -> Unit
}
}
.flowOn(mainDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 171656a..ce64ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -117,4 +117,13 @@
{ "SideFpsSensor auth duration changed: $long1" }
)
}
+
+ fun restToUnlockSettingEnabledChanged(enabled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = enabled },
+ { "restToUnlockSettingEnabled: $bool1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 23ee00d..a3029b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -146,7 +146,7 @@
null,
UserHandle.ALL
)
- userTracker.addCallback(userTrackerCallback, mainExecutor)
+ userTracker.addCallback(userTrackerCallback, backgroundExecutor)
loadSavedComponents()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 58e0428..91c86df 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -261,7 +262,7 @@
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
- private InputMonitor mInputMonitor;
+ private InputMonitorCompat mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private NavigationEdgeBackPlugin mEdgeBackPlugin;
@@ -665,10 +666,8 @@
}
// Register input event receiver
- mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
- "edge-swipe", mDisplayId);
- mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
- mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+ mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
+ mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace35..21de185 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -26,6 +26,8 @@
import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
@@ -95,6 +97,7 @@
private Future mLastShowWarningTask;
private boolean mEnableSkinTemperatureWarning;
private boolean mEnableUsbTemperatureAlarm;
+ private final HandlerThread mHandlerThread;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -167,6 +170,8 @@
mPowerManager = powerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUserTracker = userTracker;
+ mHandlerThread = new HandlerThread("PowerUI");
+ mHandlerThread.start();
}
public void start() {
@@ -185,7 +190,8 @@
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
// Check to see if we need to let the user know that the phone previously shut down due
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index d9e3e55..3f8834a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -119,6 +119,11 @@
}
}
+ /** Wakes up the device for the Side FPS acquisition event. */
+ fun wakeUpForSideFingerprintAcquisition() {
+ repository.wakeUp("SFPS_FP_ACQUISITION_STARTED", PowerManager.WAKE_REASON_BIOMETRIC)
+ }
+
/**
* Called from [KeyguardService] to inform us that the device has started waking up. This is the
* canonical source of wakefulness information for System UI. This method should not be called
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8e1b00d..7a4be3f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -23,11 +23,11 @@
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.systemui.res.R
-import com.android.systemui.animation.view.LaunchableFrameLayout
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,7 +35,7 @@
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
defStyleRes: Int = 0
-) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
private var configuration: Configuration
private var iconMargin = 0
@@ -43,6 +43,8 @@
private var iconColor = 0
private val iconsContainer: LinearLayout
+ val launchableContentView
+ get() = iconsContainer
var privacyList = emptyList<PrivacyItem>()
set(value) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index 76ef8a2..f121630 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -26,9 +26,9 @@
import android.os.UserHandle
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
-import android.view.View
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
+import androidx.core.view.isVisible
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.appops.AppOpsController
@@ -214,7 +214,7 @@
* @param context A context to use to create the dialog.
* @see filterAndSelect
*/
- fun showDialog(context: Context, view: View? = null) {
+ fun showDialog(context: Context, privacyChip: OngoingPrivacyChip? = null) {
dismissDialog()
backgroundExecutor.execute {
val usage = permGroupUsage()
@@ -277,8 +277,8 @@
)
d.setShowForAllUsers(true)
d.addOnDismissListener(onDialogDismissed)
- if (view != null) {
- val controller = getPrivacyDialogController(view)
+ if (privacyChip != null) {
+ val controller = getPrivacyDialogController(privacyChip)
if (controller == null) {
d.show()
} else {
@@ -296,10 +296,13 @@
}
}
- private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
- val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+ private fun getPrivacyDialogController(
+ source: OngoingPrivacyChip
+ ): DialogLaunchAnimator.Controller? {
+ val delegate =
+ DialogLaunchAnimator.Controller.fromView(source.launchableContentView) ?: return null
return object : DialogLaunchAnimator.Controller by delegate {
- override fun shouldAnimateExit() = false
+ override fun shouldAnimateExit() = source.isVisible
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 592cb3b..211b4594 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -192,6 +192,7 @@
private DialogLaunchAnimator mDialogLaunchAnimator;
private boolean mHasWifiEntries;
private WifiStateWorker mWifiStateWorker;
+ private boolean mHasActiveSubId;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -299,6 +300,7 @@
mExecutor);
// Listen the subscription changes
mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+ refreshHasActiveSubId();
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mOnSubscriptionsChangedListener);
mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -901,18 +903,22 @@
* @return whether there is the carrier item in the slice.
*/
boolean hasActiveSubId() {
- if (mSubscriptionManager == null) {
- if (DEBUG) {
- Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
- }
+ if (isAirplaneModeEnabled() || mTelephonyManager == null) {
return false;
}
- if (isAirplaneModeEnabled() || mTelephonyManager == null
- || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
- return false;
+ return mHasActiveSubId;
+ }
+
+ private void refreshHasActiveSubId() {
+ if (mSubscriptionManager == null) {
+ mHasActiveSubId = false;
+ Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
+ return;
}
- return true;
+
+ mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
+ Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
}
/**
@@ -1204,6 +1210,7 @@
@Override
public void onSubscriptionsChanged() {
+ refreshHasActiveSubId();
updateListener();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index e2959fe..1c37908 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -23,16 +23,22 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -47,6 +53,8 @@
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ sceneContainerFlags: SceneContainerFlags,
+ sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
private var notificationPresenter: NotificationPresenter? = null
@@ -58,11 +66,28 @@
/**
* True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
* false if the bouncer is visible.
- *
- * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
*/
val isLockscreenOrShadeVisible: StateFlow<Boolean> =
- windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+ if (!sceneContainerFlags.isEnabled()) {
+ windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+ } else {
+ sceneInteractorProvider
+ .get()
+ .transitionState
+ .map { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen
+ is ObservableTransitionState.Transition ->
+ state.toScene == SceneKey.Shade ||
+ state.toScene == SceneKey.Lockscreen ||
+ state.fromScene == SceneKey.Shade ||
+ state.fromScene == SceneKey.Lockscreen
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
+ }
/**
* True if lockscreen (including AOD) or the shade is visible **and** the user is currently
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index c96651c..995059d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -313,7 +313,7 @@
}
applicationScope.launch {
- keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+ keyguardInteractor.isDozing.collect { isDozing ->
falsingCollector.setShowingAod(isDozing)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
new file mode 100644
index 0000000..f71a401d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.shade
+
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** A base class for non-empty implementations of ShadeController. */
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class BaseShadeControllerImpl(
+ private val touchLog: LogBuffer,
+ protected val commandQueue: CommandQueue,
+ protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ protected val notificationShadeWindowController: NotificationShadeWindowController,
+ protected val assistManagerLazy: Lazy<AssistManager>
+) : ShadeController {
+ protected lateinit var notifPresenter: NotificationPresenter
+ /** Runnables to run after completing a collapse of the shade. */
+ private val postCollapseActions = ArrayList<Runnable>()
+
+ override fun start() {
+ logTouchesTo(touchLog)
+ }
+
+ final override fun animateExpandShade() {
+ if (isShadeEnabled) {
+ expandToNotifications()
+ }
+ }
+
+ /** Expand the shade with notifications visible. */
+ protected abstract fun expandToNotifications()
+
+ final override fun animateExpandQs() {
+ if (isShadeEnabled) {
+ expandToQs()
+ }
+ }
+
+ /** Expand the shade showing only quick settings. */
+ protected abstract fun expandToQs()
+
+ final override fun addPostCollapseAction(action: Runnable) {
+ postCollapseActions.add(action)
+ }
+
+ protected fun runPostCollapseActions() {
+ val clonedList: ArrayList<Runnable> = ArrayList(postCollapseActions)
+ postCollapseActions.clear()
+ for (r in clonedList) {
+ r.run()
+ }
+ statusBarKeyguardViewManager.readyForKeyguardDone()
+ }
+
+ final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
+ if (!this.notifPresenter.isCollapsing()) {
+ onClosingFinished()
+ }
+ if (launchIsFullScreen) {
+ instantCollapseShade()
+ }
+ }
+ final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
+ if (
+ notifPresenter.isPresenterFullyCollapsed() &&
+ !notifPresenter.isCollapsing() &&
+ isLaunchForActivity
+ ) {
+ onClosingFinished()
+ } else {
+ collapseShade(true /* animate */)
+ }
+ }
+
+ protected fun onClosingFinished() {
+ runPostCollapseActions()
+ if (!this.notifPresenter.isPresenterFullyCollapsed()) {
+ // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+ // the closing
+ notificationShadeWindowController.setNotificationShadeFocusable(true)
+ }
+ }
+
+ override fun setNotificationPresenter(presenter: NotificationPresenter) {
+ notifPresenter = presenter
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 44cb0d6..97ec3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -22,6 +22,7 @@
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -33,6 +34,7 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -49,7 +51,7 @@
private val powerManager: PowerManager,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
- private lateinit var communalContainerView: View
+ private var communalContainerView: View? = null
/**
* The width of the area in which a right edge swipe can open the hub, in pixels. Read from
@@ -108,6 +110,11 @@
return communalInteractor.isCommunalEnabled && isComposeAvailable()
}
+ /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */
+ fun enabledState(): StateFlow<Boolean> {
+ return communalInteractor.communalEnabledState
+ }
+
/**
* Creates the container view containing the glanceable hub UI.
*
@@ -125,42 +132,44 @@
if (!isEnabled()) {
throw RuntimeException("Glanceable hub is not enabled")
}
- if (::communalContainerView.isInitialized) {
+ if (communalContainerView != null) {
throw RuntimeException("Communal view has already been initialized")
}
communalContainerView = containerView
rightEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_right_edge_swipe_region_width
)
topEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_top_edge_swipe_region_height
)
bottomEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_bottom_edge_swipe_region_height
)
collectFlow(
- communalContainerView,
+ containerView,
keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
{ anyBouncerShowing = it }
)
- collectFlow(
- communalContainerView,
- communalInteractor.isCommunalShowing,
- { hubShowing = it }
- )
- collectFlow(
- communalContainerView,
- shadeInteractor.isAnyFullyExpanded,
- { shadeShowing = it }
- )
+ collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it })
+ collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it })
- return communalContainerView
+ communalContainerView = containerView
+
+ return containerView
+ }
+
+ /** Removes the container view from its parent. */
+ fun disposeView() {
+ communalContainerView?.let {
+ (it.parent as ViewGroup).removeView(it)
+ communalContainerView = null
+ }
}
/**
@@ -173,10 +182,10 @@
* to be fully in control of its own touch handling.
*/
fun onTouchEvent(ev: MotionEvent): Boolean {
- if (!::communalContainerView.isInitialized) {
- return false
- }
+ return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+ }
+ private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
val isUp = ev.actionMasked == MotionEvent.ACTION_UP
val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
@@ -190,7 +199,7 @@
if (hubShowing && isDown) {
val y = ev.rawY
val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
- val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth
+ val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth
if (topSwipe || bottomSwipe) {
// Don't intercept touches at the top/bottom edge so that swipes can open the
@@ -200,7 +209,7 @@
if (!hubOccluded) {
isTrackingHubTouch = true
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -209,7 +218,7 @@
if (isUp || isCancel) {
isTrackingHubTouch = false
}
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -223,11 +232,10 @@
if (!isTrackingOpenGesture && isDown) {
val x = ev.rawX
- val inOpeningSwipeRegion: Boolean =
- x >= communalContainerView.width - rightEdgeSwipeRegionWidth
+ val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
if (inOpeningSwipeRegion && !hubOccluded) {
isTrackingOpenGesture = true
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -236,7 +244,7 @@
if (isUp || isCancel) {
isTrackingOpenGesture = false
}
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -249,8 +257,8 @@
* Dispatches the touch event to the communal container and sends a user activity event to reset
* the screen timeout.
*/
- private fun dispatchTouchEvent(ev: MotionEvent) {
- communalContainerView.dispatchTouchEvent(ev)
+ private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
+ view.dispatchTouchEvent(ev)
powerManager.userActivity(
SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aeccf00..c0ceba3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2883,7 +2883,9 @@
private void onTrackingStarted() {
endClosing();
mShadeRepository.setLegacyShadeTracking(true);
- mTrackingStartedListener.onTrackingStarted();
+ if (mTrackingStartedListener != null) {
+ mTrackingStartedListener.onTrackingStarted();
+ }
notifyExpandingStarted();
updateExpansionAndVisibility();
mScrimController.onTrackingStarted();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 8c852cd..5ecc54b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -272,6 +272,14 @@
return result;
}
+ /**
+ * Handle a touch event while dreaming by forwarding the event to the content view.
+ * @param event The event to forward.
+ */
+ public void handleDreamTouch(MotionEvent event) {
+ mView.dispatchTouchEvent(event);
+ }
+
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -597,16 +605,21 @@
* The layout lives in {@link R.id.communal_ui_stub}.
*/
public void setupCommunalHubLayout() {
- if (!mGlanceableHubContainerController.isEnabled()) {
- return;
- }
-
- // Replace the placeholder view with the communal UI.
- View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
- int index = mView.indexOfChild(communalPlaceholder);
- mView.removeView(communalPlaceholder);
-
- mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index);
+ collectFlow(
+ mView,
+ mGlanceableHubContainerController.enabledState(),
+ isEnabled -> {
+ if (isEnabled) {
+ View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+ int index = mView.indexOfChild(communalPlaceholder);
+ mView.addView(
+ mGlanceableHubContainerController.initView(mView.getContext()),
+ index);
+ } else {
+ mGlanceableHubContainerController.disposeView();
+ }
+ }
+ );
}
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 2c4b0b9..ec4b23a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,10 +33,20 @@
* {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
*/
public interface ShadeController extends CoreStartable {
- /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+ /**
+ * True if the shade UI is enabled on this particular Android variant and false otherwise.
+ *
+ * @deprecated use ShadeInteractor instead
+ */
+ @Deprecated
boolean isShadeEnabled();
- /** Make our window larger and the shade expanded */
+ /**
+ * Make our window larger and the shade expanded
+ *
+ * @deprecated will no longer be needed when keyguard is a sibling view to the shade
+ */
+ @Deprecated
void instantExpandShade();
/** Collapse the shade instantly with no animation. */
@@ -74,13 +84,28 @@
/** Expand the shade with quick settings expanded with an animation. */
void animateExpandQs();
- /** Posts a request to collapse the shade. */
+ /**
+ * Posts a request to collapse the shade.
+ *
+ * @deprecated use #animateCollapseShade
+ */
+ @Deprecated
void postAnimateCollapseShade();
- /** Posts a request to force collapse the shade. */
+ /**
+ * Posts a request to force collapse the shade.
+ *
+ * @deprecated use #animateForceCollapseShade
+ */
+ @Deprecated
void postAnimateForceCollapseShade();
- /** Posts a request to expand the shade to quick settings. */
+ /**
+ * Posts a request to expand the shade to quick settings.
+ *
+ * @deprecated use #animateExpandQs
+ */
+ @Deprecated
void postAnimateExpandQs();
/** Cancels any ongoing expansion touch handling and collapses the shade. */
@@ -90,26 +115,29 @@
* If the shade is not fully expanded, collapse it animated.
*
* @return Seems to always return false
+ * @deprecated use {@link #collapseShade()} instead
*/
+ @Deprecated
boolean closeShadeIfOpen();
/**
- * Returns whether the shade state is the keyguard or not.
- */
- boolean isKeyguard();
-
- /**
* Returns whether the shade is currently open.
* Even though in the current implementation shade is in expanded state on keyguard, this
* method makes distinction between shade being truly open and plain keyguard state:
* - if QS and notifications are visible on the screen, return true
* - for any other state, including keyguard, return false
+ *
+ * @deprecated will be replaced by ShadeInteractor once scene container launches
*/
+ @Deprecated
boolean isShadeFullyOpen();
/**
* Returns whether shade or QS are currently opening or collapsing.
+ *
+ * @deprecated will be replaced by ShadeInteractor once scene container launches
*/
+ @Deprecated
boolean isExpandingOrCollapsing();
/**
@@ -127,37 +155,67 @@
*/
void addPostCollapseAction(Runnable action);
- /** Run all of the runnables added by {@link #addPostCollapseAction}. */
- void runPostCollapseRunnables();
-
/**
* Close the shade if it was open
*
* @return true if the shade was open, else false
*/
- boolean collapseShade();
+ void collapseShade();
/**
* If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
* the shade. Post collapse runnables will be executed
*
* @param animate true to animate the collapse, false for instantaneous collapse
+ * @deprecated call either #animateCollapseShade or #instantCollapseShade
*/
+ @Deprecated
void collapseShade(boolean animate);
- /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+ /**
+ * Calls #collapseShade if already on the main thread. If not, posts a call to it.
+ * @deprecated call #collapseShade
+ */
+ @Deprecated
void collapseOnMainThread();
- /** Makes shade expanded but not visible. */
+ /**
+ * If necessary, instantly collapses the shade for an activity start, otherwise runs the
+ * post-collapse runnables. Instant collapse is ok here, because the purpose is to have the
+ * shade collapsed when the user returns to SysUI from the launched activity.
+ */
+ void collapseShadeForActivityStart();
+
+ /**
+ * Makes shade expanded but not visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
void makeExpandedInvisible();
- /** Makes shade expanded and visible. */
+ /**
+ * Makes shade expanded and visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
void makeExpandedVisible(boolean force);
- /** Returns whether the shade is expanded and visible. */
+ /**
+ * Returns whether the shade is expanded and visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
boolean isExpandedVisible();
- /** Handle status bar touch event. */
+ /**
+ * Handle status bar touch event.
+ *
+ * @deprecated only called by CentralSurfaces, which is being deleted
+ */
+ @Deprecated
void onStatusBarTouch(MotionEvent event);
/** Called when a launch animation was cancelled. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 82959ee..08a0c93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -42,9 +42,6 @@
override fun closeShadeIfOpen(): Boolean {
return false
}
- override fun isKeyguard(): Boolean {
- return false
- }
override fun isShadeFullyOpen(): Boolean {
return false
}
@@ -53,12 +50,10 @@
}
override fun postOnShadeExpanded(action: Runnable?) {}
override fun addPostCollapseAction(action: Runnable?) {}
- override fun runPostCollapseRunnables() {}
- override fun collapseShade(): Boolean {
- return false
- }
+ override fun collapseShade() {}
override fun collapseShade(animate: Boolean) {}
override fun collapseOnMainThread() {}
+ override fun collapseShadeForActivityStart() {}
override fun makeExpandedInvisible() {}
override fun makeExpandedVisible(force: Boolean) {}
override fun isExpandedVisible(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index fdc7eec..e8d9c35 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -33,7 +33,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -44,14 +43,13 @@
import dagger.Lazy;
-import java.util.ArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/** An implementation of {@link ShadeController}. */
@SysUISingleton
-public final class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl extends BaseShadeControllerImpl {
private static final String TAG = "ShadeControllerImpl";
private static final boolean SPEW = false;
@@ -60,7 +58,6 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
- private final LogBuffer mTouchLog;
private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -73,12 +70,9 @@
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<NotificationGutsManager> mGutsManager;
- private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
-
private boolean mExpandedVisible;
private boolean mLockscreenOrShadeVisible;
- private NotificationPresenter mPresenter;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private ShadeVisibilityListener mShadeVisibilityListener;
@@ -99,9 +93,13 @@
Lazy<AssistManager> assistManagerLazy,
Lazy<NotificationGutsManager> gutsManager
) {
+ super(touchLog,
+ commandQueue,
+ statusBarKeyguardViewManager,
+ notificationShadeWindowController,
+ assistManagerLazy);
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
- mTouchLog = touchLog;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
@@ -117,7 +115,7 @@
@Override
public boolean isShadeEnabled() {
- return true;
+ return mCommandQueue.panelsEnabled() && mDeviceProvisionedController.isCurrentUserSetup();
}
@Override
@@ -125,20 +123,16 @@
// Make our window larger and the panel expanded.
makeExpandedVisible(true /* force */);
getShadeViewController().expand(false /* animate */);
- mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+ getCommandQueue().recomputeDisableFlags(mDisplayId, false /* animate */);
}
@Override
public void animateCollapseShade(int flags, boolean force, boolean delayed,
float speedUpFactor) {
if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
- runPostCollapseRunnables();
+ runPostCollapseActions();
return;
}
- if (SPEW) {
- Log.d(TAG,
- "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
- }
if (getNotificationShadeWindowView() != null
&& getShadeViewController().canBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
@@ -151,28 +145,19 @@
}
@Override
- public void animateExpandShade() {
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
+ protected void expandToNotifications() {
getShadeViewController().expandToNotifications();
}
@Override
- public void animateExpandQs() {
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
- // Settings are not available in setup
- if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
+ protected void expandToQs() {
getShadeViewController().expandToQs();
}
@Override
public boolean closeShadeIfOpen() {
if (!getShadeViewController().isFullyCollapsed()) {
- mCommandQueue.animateCollapsePanels(
+ getCommandQueue().animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
notifyVisibilityChanged(false);
mAssistManagerLazy.get().hideAssist();
@@ -181,11 +166,6 @@
}
@Override
- public boolean isKeyguard() {
- return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- }
-
- @Override
public boolean isShadeFullyOpen() {
return getShadeViewController().isShadeFullyExpanded();
}
@@ -224,46 +204,34 @@
}
@Override
- public void addPostCollapseAction(Runnable action) {
- mPostCollapseRunnables.add(action);
+ public void collapseShade() {
+ collapseShadeInternal();
}
- @Override
- public void runPostCollapseRunnables() {
- ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
- mPostCollapseRunnables.clear();
- int size = clonedList.size();
- for (int i = 0; i < size; i++) {
- clonedList.get(i).run();
- }
- mStatusBarKeyguardViewManager.readyForKeyguardDone();
- }
-
- @Override
- public boolean collapseShade() {
+ private boolean collapseShadeInternal() {
if (!getShadeViewController().isFullyCollapsed()) {
// close the shade if it was open
animateCollapseShadeForcedDelayed();
notifyVisibilityChanged(false);
-
return true;
} else {
return false;
}
}
+
@Override
public void collapseShade(boolean animate) {
if (animate) {
- boolean willCollapse = collapseShade();
+ boolean willCollapse = collapseShadeInternal();
if (!willCollapse) {
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
- } else if (!mPresenter.isPresenterFullyCollapsed()) {
+ } else if (!getNotifPresenter().isPresenterFullyCollapsed()) {
instantCollapseShade();
notifyVisibilityChanged(false);
} else {
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
}
@@ -296,46 +264,16 @@
}
}
- private void onClosingFinished() {
- runPostCollapseRunnables();
- if (!mPresenter.isPresenterFullyCollapsed()) {
- // if we set it not to be focusable when collapsing, we have to undo it when we aborted
- // the closing
- mNotificationShadeWindowController.setNotificationShadeFocusable(true);
- }
- }
-
- @Override
- public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
- if (mPresenter.isPresenterFullyCollapsed()
- && !mPresenter.isCollapsing()
- && isLaunchForActivity) {
- onClosingFinished();
- } else {
- collapseShade(true /* animate */);
- }
- }
-
- @Override
- public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
- if (!mPresenter.isCollapsing()) {
- onClosingFinished();
- }
- if (launchIsFullScreen) {
- instantCollapseShade();
- }
- }
-
@Override
public void instantCollapseShade() {
getShadeViewController().instantCollapse();
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
@Override
public void makeExpandedVisible(boolean force) {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+ if (!force && (mExpandedVisible || !getCommandQueue().panelsEnabled())) {
return;
}
@@ -346,7 +284,7 @@
mNotificationShadeWindowController.setPanelVisible(true);
notifyVisibilityChanged(true);
- mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+ getCommandQueue().recomputeDisableFlags(mDisplayId, !force /* animate */);
notifyExpandedVisibleChanged(true);
}
@@ -377,9 +315,9 @@
-1 /* y */,
true /* resetMenu */);
- runPostCollapseRunnables();
+ runPostCollapseActions();
notifyExpandedVisibleChanged(false);
- mCommandQueue.recomputeDisableFlags(
+ getCommandQueue().recomputeDisableFlags(
mDisplayId,
getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
@@ -421,11 +359,6 @@
}
@Override
- public void setNotificationPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- }
-
- @Override
public void setNotificationShadeWindowViewController(
NotificationShadeWindowViewController controller) {
mNotificationShadeWindowViewController = controller;
@@ -441,8 +374,8 @@
@Override
public void start() {
- TouchLogger.logTouchesTo(mTouchLog);
- getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+ super.start();
+ getShadeViewController().setTrackingStartedListener(this::runPostCollapseActions);
getShadeViewController().setOpenCloseListener(
new OpenCloseListener() {
@Override
@@ -456,4 +389,16 @@
}
});
}
+
+ @Override
+ public void collapseShadeForActivityStart() {
+ if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ animateCollapseShadeForcedDelayed();
+ } else {
+ // Do it after DismissAction has been processed to conserve the
+ // needed ordering.
+ mMainExecutor.execute(this::runPostCollapseActions);
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
new file mode 100644
index 0000000..10b9db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.shade
+
+import android.view.MotionEvent
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.ShadeTouchLog
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * Implementation of ShadeController backed by scenes instead of NPVC.
+ *
+ * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ShadeControllerSceneImpl
+@Inject
+constructor(
+ @Background private val scope: CoroutineScope,
+ private val shadeInteractor: ShadeInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val notificationStackScrollLayout: NotificationStackScrollLayout,
+ @ShadeTouchLog private val touchLog: LogBuffer,
+ commandQueue: CommandQueue,
+ statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ notificationShadeWindowController: NotificationShadeWindowController,
+ assistManagerLazy: Lazy<AssistManager>,
+) :
+ BaseShadeControllerImpl(
+ touchLog,
+ commandQueue,
+ statusBarKeyguardViewManager,
+ notificationShadeWindowController,
+ assistManagerLazy,
+ ) {
+
+ init {
+ scope.launch {
+ shadeInteractor.isAnyExpanded.collect {
+ if (!it) {
+ runPostCollapseActions()
+ }
+ }
+ }
+ }
+
+ override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
+
+ override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
+
+ override fun isExpandingOrCollapsing(): Boolean =
+ shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+
+ override fun instantExpandShade() {
+ // Do nothing
+ }
+
+ override fun instantCollapseShade() {
+ // TODO(b/315921512) add support for instant transition
+ sceneInteractor.changeScene(
+ SceneModel(getCollapseDestinationScene(), "instant"),
+ "hide shade"
+ )
+ }
+
+ override fun animateCollapseShade(
+ flags: Int,
+ force: Boolean,
+ delayed: Boolean,
+ speedUpFactor: Float
+ ) {
+ if (!force && !shadeInteractor.isAnyExpanded.value) {
+ runPostCollapseActions()
+ return
+ }
+ if (
+ shadeInteractor.isAnyExpanded.value &&
+ flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0
+ ) {
+ // release focus immediately to kick off focus change transition
+ notificationShadeWindowController.setNotificationShadeFocusable(false)
+ notificationStackScrollLayout.cancelExpandHelper()
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.Shade, null),
+ "ShadeController.animateExpandShade"
+ )
+ if (delayed) {
+ scope.launch {
+ delay(125)
+ animateCollapseShadeInternal()
+ }
+ } else {
+ animateCollapseShadeInternal()
+ }
+ }
+ }
+
+ private fun animateCollapseShadeInternal() {
+ sceneInteractor.changeScene(
+ SceneModel(getCollapseDestinationScene(), "ShadeController.animateCollapseShade"),
+ "ShadeController.animateCollapseShade"
+ )
+ }
+
+ private fun getCollapseDestinationScene(): SceneKey {
+ return if (deviceEntryInteractor.isDeviceEntered.value) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ }
+
+ override fun cancelExpansionAndCollapseShade() {
+ // TODO do we need to actually cancel the touch session?
+ animateCollapseShade()
+ }
+
+ override fun closeShadeIfOpen(): Boolean {
+ if (shadeInteractor.isAnyExpanded.value) {
+ commandQueue.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */
+ )
+ assistManagerLazy.get().hideAssist()
+ }
+ return false
+ }
+
+ override fun collapseShade() {
+ animateCollapseShadeForcedDelayed()
+ }
+
+ override fun collapseShade(animate: Boolean) {
+ if (animate) {
+ animateCollapseShade()
+ } else {
+ instantCollapseShade()
+ }
+ }
+
+ override fun collapseOnMainThread() {
+ // TODO if this works with delegation alone, we can deprecate and delete
+ collapseShade()
+ }
+
+ override fun expandToNotifications() {
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.Shade, null),
+ "ShadeController.animateExpandShade"
+ )
+ }
+
+ override fun expandToQs() {
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.QuickSettings, null),
+ "ShadeController.animateExpandQs"
+ )
+ }
+
+ override fun setVisibilityListener(listener: ShadeVisibilityListener) {
+ scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun collapseShadeForActivityStart() {
+ if (shadeInteractor.isAnyExpanded.value) {
+ animateCollapseShadeForcedDelayed()
+ } else {
+ runPostCollapseActions()
+ }
+ }
+
+ override fun postAnimateCollapseShade() {
+ animateCollapseShade()
+ }
+
+ override fun postAnimateForceCollapseShade() {
+ animateCollapseShadeForced()
+ }
+
+ override fun postAnimateExpandQs() {
+ expandToQs()
+ }
+
+ override fun postOnShadeExpanded(action: Runnable) {
+ // TODO verify that clicking "reply" in a work profile notification launches the app
+ // TODO verify that there's not a way to replace and deprecate this method
+ scope.launch {
+ shadeInteractor.isAnyFullyExpanded.first { it }
+ action.run()
+ }
+ }
+
+ override fun makeExpandedInvisible() {
+ // Do nothing
+ }
+
+ override fun makeExpandedVisible(force: Boolean) {
+ // Do nothing
+ }
+
+ override fun isExpandedVisible(): Boolean {
+ return sceneInteractor.desiredScene.value.key != SceneKey.Gone
+ }
+
+ override fun onStatusBarTouch(event: MotionEvent) {
+ // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index c057147..fc2c3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -53,6 +53,20 @@
@Provides
@SysUISingleton
+ fun provideShadeController(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeControllerSceneImpl>,
+ sceneContainerOff: Provider<ShadeControllerImpl>
+ ): ShadeController {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
fun provideShadeAnimationInteractor(
sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
@@ -79,8 +93,4 @@
abstract fun bindsShadeViewController(
notificationPanelViewController: NotificationPanelViewController
): ShadeViewController
-
- @Binds
- @SysUISingleton
- abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
}
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 31a4de4..43ede2a 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
@@ -29,7 +29,7 @@
val isShadeEnabled: StateFlow<Boolean>
/** Whether either the shade or QS is fully expanded. */
- val isAnyFullyExpanded: Flow<Boolean>
+ val isAnyFullyExpanded: StateFlow<Boolean>
/** Whether the Shade is fully expanded. */
val isShadeFullyExpanded: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6defbcf..55dd674 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -34,7 +34,7 @@
override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean
override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
- override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+ override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 6407b5a..a71cf95 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -55,12 +55,20 @@
private val baseShadeInteractor: BaseShadeInteractor,
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
- disableFlagsRepository.disableFlags
- .map { it.isShadeEnabled() }
+ combine(
+ deviceProvisioningRepository.isFactoryResetProtectionActive,
+ disableFlagsRepository.disableFlags,
+ ) { isFrpActive, isDisabledByFlags ->
+ isDisabledByFlags.isShadeEnabled() && !isFrpActive
+ }
+ .distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
- override val isAnyFullyExpanded: Flow<Boolean> =
- anyExpansion.map { it >= 1f }.distinctUntilChanged()
+ override val isAnyFullyExpanded: StateFlow<Boolean> =
+ anyExpansion
+ .map { it >= 1f }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
override val isShadeFullyExpanded: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 909cff37..e598242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -542,7 +542,19 @@
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_access_google_assistant),
Arrays.asList(
- Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON)))
+ Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))),
+ /* Lock screen: Meta + L */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_lock_screen),
+ Arrays.asList(
+ Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))),
+ /* Pull up Notes app for quick memo: Meta + Ctrl + N */
+ new ShortcutKeyGroupMultiMappingInfo(
+ context.getString(R.string.group_system_quick_memo),
+ Arrays.asList(
+ Pair.create(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON)))
);
for (ShortcutKeyGroupMultiMappingInfo info : infoList) {
systemGroup.addItem(info.getShortcutMultiMappingInfo());
@@ -584,11 +596,17 @@
new ArrayList<>());
// System multitasking shortcuts:
+ // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow
+ // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow
// Switch from Split screen to full screen: Meta + Ctrl + Up arrow
String[] shortcutLabels = {
+ context.getString(R.string.system_multitasking_rhs),
+ context.getString(R.string.system_multitasking_lhs),
context.getString(R.string.system_multitasking_full_screen),
};
int[] keyCodes = {
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_UP,
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index fc84973..28d4457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -35,7 +35,6 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -61,7 +60,6 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -73,6 +71,7 @@
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -85,6 +84,8 @@
import dalvik.annotation.optimization.NeverCompile;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -99,8 +100,6 @@
import javax.inject.Inject;
-import kotlin.Unit;
-
/** Platform implementation of the network controller. **/
@SysUISingleton
public class NetworkControllerImpl extends BroadcastReceiver
@@ -350,7 +349,7 @@
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+ mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 2d5afd5..3cdb2cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,8 +21,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -53,14 +51,11 @@
*/
private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
- private final FeatureFlags mFeatureFlags;
-
@Inject
public GroupExpansionManagerImpl(DumpManager dumpManager,
- GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
+ GroupMembershipManager groupMembershipManager) {
mDumpManager = dumpManager;
mGroupMembershipManager = groupMembershipManager;
- mFeatureFlags = featureFlags;
}
/**
@@ -86,10 +81,8 @@
};
public void attach(NotifPipeline pipeline) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- mDumpManager.registerDumpable(this);
- pipeline.addOnBeforeRenderListListener(mNotifTracker);
- }
+ mDumpManager.registerDumpable(this);
+ pipeline.addOnBeforeRenderListListener(mNotifTracker);
}
@Override
@@ -105,8 +98,7 @@
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
- && entry.getParent() == null) {
+ if (entry.getParent() == null) {
if (expanded) {
throw new IllegalArgumentException("Cannot expand group that is not attached");
} else {
@@ -124,7 +116,7 @@
}
// Only notify listeners if something changed.
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
+ if (changed) {
sendOnGroupExpandedChange(entry, expanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index cb79353..da12479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,8 +22,6 @@
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,25 +36,17 @@
*/
@SysUISingleton
public class GroupMembershipManagerImpl implements GroupMembershipManager {
- FeatureFlagsClassic mFeatureFlags;
-
@Inject
- public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
- mFeatureFlags = featureFlags;
- }
+ public GroupMembershipManagerImpl() {}
@Override
public boolean isGroupSummary(@NonNull NotificationEntry entry) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- if (entry.getParent() == null) {
- // The entry is not attached, so it doesn't count.
- return false;
- }
- // If entry is a summary, its parent is a GroupEntry with summary = entry.
- return entry.getParent().getSummary() == entry;
- } else {
- return getGroupSummary(entry) == entry;
+ if (entry.getParent() == null) {
+ // The entry is not attached, so it doesn't count.
+ return false;
}
+ // If entry is a summary, its parent is a GroupEntry with summary = entry.
+ return entry.getParent().getSummary() == entry;
}
@Nullable
@@ -70,12 +60,8 @@
@Override
public boolean isChildInGroup(@NonNull NotificationEntry entry) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- // An entry is a child if it's not a summary or top level entry, but it is attached.
- return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
- } else {
- return !isTopLevelEntry(entry);
- }
+ // An entry is a child if it's not a summary or top level entry, but it is attached.
+ return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64..342828c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -6,6 +6,7 @@
import android.net.Uri
import android.os.Handler
import android.os.HandlerExecutor
+import android.os.HandlerThread
import android.os.UserHandle
import android.provider.Settings
import com.android.keyguard.KeyguardUpdateMonitor
@@ -87,6 +88,7 @@
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
private val onStateChangedListeners = ListenerSet<Consumer<String>>()
private var hideSilentNotificationsOnLockscreen: Boolean = false
+ private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis")
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
@@ -154,7 +156,9 @@
notifyStateChanged("onStatusBarUpcomingStateChanged")
}
})
- userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
+ handlerThread.start()
+ userTracker.addCallback(userTrackerCallback,
+ HandlerExecutor(handlerThread.getThreadHandler()))
}
override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 4349b3b..c6832bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -247,6 +247,9 @@
public void setUpWithContainer(NotificationListContainer listContainer) {
mListContainer = listContainer;
+ if (mLogging) {
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ }
}
@Override
@@ -294,7 +297,9 @@
lockscreen = mLockscreen != null && mLockscreen;
}
mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
- mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ if (mListContainer != null) {
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ }
// Sometimes, the transition from lockscreenOrShadeVisible=false ->
// lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
// events. Hence generate one ourselves to guarantee that we're reporting visible
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
index d10b556..8bc8e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -22,11 +22,9 @@
import android.view.LayoutInflater
import android.view.View
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
-import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import javax.inject.Named
/**
* Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
@@ -37,8 +35,7 @@
constructor(
@Assisted private val row: ExpandableNotificationRow,
@Assisted @InflationFlag val layoutType: Int,
- @Named(NOTIF_REMOTEVIEWS_FACTORIES)
- private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+ private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer
) : LayoutInflater.Factory2 {
override fun onCreateView(
@@ -49,7 +46,7 @@
): View? {
var handledFactory: NotifRemoteViewsFactory? = null
var result: View? = null
- for (layoutFactory in remoteViewsFactories) {
+ for (layoutFactory in notifRemoteViewsFactoryContainer.factories) {
layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
check(handledFactory == null) {
"$layoutFactory tries to produce name:$name with type:$layoutType. " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
new file mode 100644
index 0000000..99177c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+interface NotifRemoteViewsFactoryContainer {
+ val factories: Set<NotifRemoteViewsFactory>
+}
+
+class NotifRemoteViewsFactoryContainerImpl
+@Inject
+constructor(
+ featureFlags: FeatureFlags,
+ precomputedTextViewFactory: PrecomputedTextViewFactory,
+ bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
+ callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+) : NotifRemoteViewsFactoryContainer {
+ override val factories: Set<NotifRemoteViewsFactory> = buildSet {
+ add(precomputedTextViewFactory)
+ if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+ add(bigPictureLayoutInflaterFactory)
+ }
+ if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+ add(callLayoutSetDataAsyncFactory)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 46ddba4..200a08a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,26 +17,15 @@
package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import dagger.Binds;
import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Named;
/**
* Dagger Module containing notification row and view inflation implementations.
*/
@Module
public abstract class NotificationRowModule {
- public static final String NOTIF_REMOTEVIEWS_FACTORIES =
- "notif_remoteviews_factories";
/**
* Provides notification row content binder instance.
@@ -54,24 +43,11 @@
public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
NotifRemoteViewCacheImpl cacheImpl);
- /** Provides view factories to be inflated in notification content. */
- @Provides
- @ElementsIntoSet
- @Named(NOTIF_REMOTEVIEWS_FACTORIES)
- static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
- FeatureFlags featureFlags,
- PrecomputedTextViewFactory precomputedTextViewFactory,
- BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
- CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
- ) {
- final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
- replacementFactories.add(precomputedTextViewFactory);
- if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
- replacementFactories.add(bigPictureLayoutInflaterFactory);
- }
- if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
- replacementFactories.add(callLayoutSetDataAsyncFactory);
- }
- return replacementFactories;
- }
+ /**
+ * Provides notification remote view factory container
+ */
+ @Binds
+ @SysUISingleton
+ public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer(
+ NotifRemoteViewsFactoryContainerImpl containerImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 63194c3..8a56da3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -797,18 +797,7 @@
}
}
if (dismissShade) {
- if (
- shadeControllerLazy.get().isExpandedVisible &&
- !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
- ) {
- shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
- } else {
- // Do it after DismissAction has been processed to conserve the
- // needed ordering.
- postOnUiThread {
- shadeControllerLazy.get().runPostCollapseRunnables()
- }
- }
+ shadeControllerLazy.get().collapseShadeForActivityStart()
}
return deferred
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 90cba40..4019436 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
+import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.window.RemoteTransition;
@@ -277,6 +278,13 @@
void awakenDreams();
+ /**
+ * Handle a touch event while dreaming when the touch was initiated within a prescribed
+ * swipeable area. This method is provided for cases where swiping in certain areas of a dream
+ * should be handled by CentralSurfaces instead (e.g. swiping communal hub open).
+ */
+ void handleDreamTouch(MotionEvent event);
+
boolean isBouncerShowing();
boolean isBouncerShowingScrimmed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 7dc4b96..60dfaa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone
import android.content.Intent
+import android.view.MotionEvent
import androidx.lifecycle.LifecycleRegistry
import com.android.keyguard.AuthKeyguardMessageArea
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -78,6 +79,7 @@
override fun updateScrimController() {}
override fun shouldIgnoreTouch() = false
override fun isDeviceInteractive() = false
+ override fun handleDreamTouch(event: MotionEvent?) {}
override fun awakenDreams() {}
override fun isBouncerShowing() = false
override fun isBouncerShowingScrimmed() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6e3aabf..266c19c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -80,6 +80,7 @@
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
+import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.WindowInsets;
@@ -2903,6 +2904,11 @@
};
@Override
+ public void handleDreamTouch(MotionEvent event) {
+ getNotificationShadeWindowViewController().handleDreamTouch(event);
+ }
+
+ @Override
public void awakenDreams() {
mUiBgExecutor.execute(() -> {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index fe49c07..6b30326 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.content.Context
+import com.android.internal.telephony.flags.Flags
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
@@ -32,14 +33,18 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -79,6 +84,9 @@
/** Whether or not to show the slice attribution */
val showSliceAttribution: StateFlow<Boolean>
+ /** True if this connection is satellite-based */
+ val isNonTerrestrial: StateFlow<Boolean>
+
/**
* Provider name for this network connection. The name can be one of 3 values:
* 1. The default network name, if one is configured
@@ -244,6 +252,13 @@
override val showSliceAttribution: StateFlow<Boolean> =
connectionRepository.hasPrioritizedNetworkCapabilities
+ override val isNonTerrestrial: StateFlow<Boolean> =
+ if (Flags.carrierEnabledSatelliteFlag()) {
+ connectionRepository.isNonTerrestrial
+ } else {
+ MutableStateFlow(false).asStateFlow()
+ }
+
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -313,26 +328,45 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- override val signalLevelIcon: StateFlow<SignalIconModel> = run {
- val initial =
- SignalIconModel(
- level = shownLevel.value,
- numberOfLevels = numberOfLevels.value,
- showExclamationMark = showExclamationMark.value,
- carrierNetworkChange = carrierNetworkChangeActive.value,
- )
+ private val cellularIcon: Flow<SignalIconModel.Cellular> =
combine(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChangeActive,
+ ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel.Cellular(
shownLevel,
numberOfLevels,
showExclamationMark,
- carrierNetworkChangeActive,
- ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
- SignalIconModel(
- shownLevel,
- numberOfLevels,
- showExclamationMark,
- carrierNetworkChange,
- )
+ carrierNetworkChange,
+ )
+ }
+
+ private val satelliteIcon: Flow<SignalIconModel.Satellite> =
+ shownLevel.map {
+ SignalIconModel.Satellite(
+ level = it,
+ icon = SatelliteIconModel.fromSignalStrength(it)
+ ?: SatelliteIconModel.fromSignalStrength(0)!!
+ )
+ }
+
+ override val signalLevelIcon: StateFlow<SignalIconModel> = run {
+ val initial =
+ SignalIconModel.Cellular(
+ shownLevel.value,
+ numberOfLevels.value,
+ showExclamationMark.value,
+ carrierNetworkChangeActive.value,
+ )
+ isNonTerrestrial
+ .flatMapLatest { ntn ->
+ if (ntn) {
+ satelliteIcon
+ } else {
+ cellularIcon
+ }
}
.distinctUntilChanged()
.logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
index e58f081..d6b8fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
@@ -17,51 +17,94 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.model
import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
-/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
-data class SignalIconModel(
- val level: Int,
- val numberOfLevels: Int,
- val showExclamationMark: Boolean,
- val carrierNetworkChange: Boolean,
-) : Diffable<SignalIconModel> {
- // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+sealed interface SignalIconModel : Diffable<SignalIconModel> {
+ val level: Int
+
override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
- if (prevVal.level != level) {
+ logPartial(prevVal, row)
+ }
+
+ override fun logFull(row: TableRowLogger) = logFully(row)
+
+ fun logFully(row: TableRowLogger)
+
+ fun logPartial(prevVal: SignalIconModel, row: TableRowLogger)
+
+ /** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
+ data class Cellular(
+ override val level: Int,
+ val numberOfLevels: Int,
+ val showExclamationMark: Boolean,
+ val carrierNetworkChange: Boolean,
+ ) : SignalIconModel {
+ override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
+ if (prevVal !is Cellular) {
+ logFull(row)
+ } else {
+ if (prevVal.level != level) {
+ row.logChange(COL_LEVEL, level)
+ }
+ if (prevVal.numberOfLevels != numberOfLevels) {
+ row.logChange(COL_NUM_LEVELS, numberOfLevels)
+ }
+ if (prevVal.showExclamationMark != showExclamationMark) {
+ row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ }
+ if (prevVal.carrierNetworkChange != carrierNetworkChange) {
+ row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
+ }
+ }
+ }
+
+ override fun logFully(row: TableRowLogger) {
+ row.logChange(COL_TYPE, "c")
row.logChange(COL_LEVEL, level)
- }
- if (prevVal.numberOfLevels != numberOfLevels) {
row.logChange(COL_NUM_LEVELS, numberOfLevels)
- }
- if (prevVal.showExclamationMark != showExclamationMark) {
row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
- }
- if (prevVal.carrierNetworkChange != carrierNetworkChange) {
row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
}
+
+ /** Convert this model to an [Int] consumable by [SignalDrawable]. */
+ fun toSignalDrawableState(): Int =
+ if (carrierNetworkChange) {
+ SignalDrawable.getCarrierChangeState(numberOfLevels)
+ } else {
+ SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ }
}
- override fun logFull(row: TableRowLogger) {
- row.logChange(COL_LEVEL, level)
- row.logChange(COL_NUM_LEVELS, numberOfLevels)
- row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
- }
-
- /** Convert this model to an [Int] consumable by [SignalDrawable]. */
- fun toSignalDrawableState(): Int =
- if (carrierNetworkChange) {
- SignalDrawable.getCarrierChangeState(numberOfLevels)
- } else {
- SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ /**
+ * For non-terrestrial networks, we can use a resource-backed icon instead of the
+ * [SignalDrawable]-backed version above
+ */
+ data class Satellite(
+ override val level: Int,
+ val icon: Icon.Resource,
+ ) : SignalIconModel {
+ override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
+ if (prevVal !is Satellite) {
+ logFull(row)
+ } else {
+ if (prevVal.level != level) row.logChange(COL_LEVEL, level)
+ }
}
+ override fun logFully(row: TableRowLogger) {
+ row.logChange("numLevels", "HELLO")
+ row.logChange(COL_TYPE, "s")
+ row.logChange(COL_LEVEL, level)
+ }
+ }
+
companion object {
private const val COL_LEVEL = "level"
private const val COL_NUM_LEVELS = "numLevels"
private const val COL_SHOW_EXCLAMATION = "showExclamation"
private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange"
+ private const val COL_TYPE = "type"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index a1a5370..43cb38f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -59,7 +59,7 @@
str1 = parentView.getIdForLogging()
int1 = subId
int2 = icon.level
- bool1 = icon.showExclamationMark
+ bool1 = if (icon is SignalIconModel.Cellular) icon.showExclamationMark else false
},
{
"Binder[subId=$int1, viewId=$str1] received new signal icon: " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 5475528..a0c5618 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -38,6 +38,7 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -70,7 +71,7 @@
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
- val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+ val mobileDrawable = SignalDrawable(view.context)
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
@@ -138,7 +139,12 @@
viewModel.subscriptionId,
icon,
)
- mobileDrawable.level = icon.toSignalDrawableState()
+ if (icon is SignalIconModel.Cellular) {
+ iconView.setImageDrawable(mobileDrawable)
+ mobileDrawable.level = icon.toSignalDrawableState()
+ } else if (icon is SignalIconModel.Satellite) {
+ IconViewBinder.bind(icon.icon, iconView)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 60c662d..eda5c44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -33,12 +33,15 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
@@ -71,7 +74,6 @@
* model gets the exact same information, as well as allows us to log that unified state only once
* per icon.
*/
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel(
override val subscriptionId: Int,
@@ -81,6 +83,100 @@
flags: FeatureFlagsClassic,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
+ private val cellProvider by lazy {
+ CellularIconViewModel(
+ subscriptionId,
+ iconInteractor,
+ airplaneModeInteractor,
+ constants,
+ flags,
+ scope,
+ )
+ }
+
+ private val satelliteProvider by lazy {
+ CarrierBasedSatelliteViewModelImpl(
+ subscriptionId,
+ iconInteractor,
+ )
+ }
+
+ /**
+ * Similar to repository switching, this allows us to split up the logic of satellite/cellular
+ * states, since they are different by nature
+ */
+ private val vmProvider: Flow<MobileIconViewModelCommon> =
+ iconInteractor.isNonTerrestrial
+ .mapLatest { nonTerrestrial ->
+ if (nonTerrestrial) {
+ satelliteProvider
+ } else {
+ cellProvider
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)
+
+ override val isVisible: StateFlow<Boolean> =
+ vmProvider
+ .flatMapLatest { it.isVisible }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
+
+ override val contentDescription: Flow<ContentDescription> =
+ vmProvider.flatMapLatest { it.contentDescription }
+
+ override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
+
+ override val networkTypeIcon: Flow<Icon.Resource?> =
+ vmProvider.flatMapLatest { it.networkTypeIcon }
+
+ override val networkTypeBackground: StateFlow<Icon.Resource?> =
+ vmProvider
+ .flatMapLatest { it.networkTypeBackground }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activityInVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityInVisible }
+
+ override val activityOutVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityOutVisible }
+
+ override val activityContainerVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityContainerVisible }
+}
+
+/** Representation of this network when it is non-terrestrial (e.g., satellite) */
+private class CarrierBasedSatelliteViewModelImpl(
+ override val subscriptionId: Int,
+ interactor: MobileIconInteractor,
+) : MobileIconViewModelCommon {
+ override val isVisible: StateFlow<Boolean> = MutableStateFlow(true)
+ override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon
+
+ override val contentDescription: Flow<ContentDescription> =
+ MutableStateFlow(ContentDescription.Loaded(""))
+
+ /** These fields are not used for satellite icons currently */
+ override val roaming: Flow<Boolean> = flowOf(false)
+ override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
+ override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
+ override val activityInVisible: Flow<Boolean> = flowOf(false)
+ override val activityOutVisible: Flow<Boolean> = flowOf(false)
+ override val activityContainerVisible: Flow<Boolean> = flowOf(false)
+}
+
+/** Terrestrial (cellular) icon. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+private class CellularIconViewModel(
+ override val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ airplaneModeInteractor: AirplaneModeInteractor,
+ constants: ConnectivityConstants,
+ flags: FeatureFlagsClassic,
+ scope: CoroutineScope,
+) : MobileIconViewModelCommon {
override val isVisible: StateFlow<Boolean> =
if (!constants.hasDataCapabilities) {
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
index 6938d66..63566ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -28,7 +28,7 @@
fun fromConnectionState(
connectionState: SatelliteConnectionState,
signalStrength: Int,
- ): Icon? =
+ ): Icon.Resource? =
when (connectionState) {
// TODO(b/316635648): check if this should be null
SatelliteConnectionState.Unknown,
@@ -41,9 +41,13 @@
SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
}
- private fun fromSignalStrength(
+ /**
+ * Satellite icon appropriate for when we are connected. Use [fromConnectionState] for a more
+ * generally correct representation.
+ */
+ fun fromSignalStrength(
signalStrength: Int,
- ): Icon? =
+ ): Icon.Resource? =
// TODO(b/316634365): these need content descriptions
when (signalStrength) {
// No signal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index ae58398..352413e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.text.Html
import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -28,6 +29,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
@@ -116,14 +118,31 @@
it.signalLevelIcon,
mobileDataContentName,
) { networkNameModel, signalIcon, dataContentDescription ->
- val secondary =
- mobileDataContentConcat(networkNameModel.name, dataContentDescription)
- InternetTileModel.Active(
- secondaryTitle = secondary,
- icon = SignalIcon(signalIcon.toSignalDrawableState()),
- stateDescription = ContentDescription.Loaded(secondary.toString()),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = SignalIcon(signalIcon.toSignalDrawableState()),
+ stateDescription = ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(context)
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 20d1fff..a2d8d15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -28,6 +28,8 @@
import android.icu.text.DateTimePatternGenerator;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -48,11 +50,11 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -106,6 +108,7 @@
private final int mAmPmStyle;
private boolean mShowSeconds;
private Handler mSecondsHandler;
+ private HandlerThread mHandlerThread;
// Fields to cache the width so the clock remains at an approximately constant width
private int mCharsAtCurrentWidth = -1;
@@ -146,6 +149,8 @@
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
mUserTracker = Dependency.get(UserTracker.class);
+ mHandlerThread = new HandlerThread("Clock");
+ mHandlerThread.start();
setIncludeFontPadding(false);
}
@@ -205,7 +210,8 @@
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_HIDE_LIST);
mCommandQueue.addCallback(this);
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mCurrentUserId = mUserTracker.getUserId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b7d8ee3..a7440d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -51,6 +53,7 @@
private final UserTracker mUserTracker;
private AlarmManager mAlarmManager;
private AlarmManager.AlarmClockInfo mNextAlarm;
+ private HandlerThread mHandlerThread;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -75,7 +78,10 @@
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mHandlerThread = new HandlerThread("NextAlarmControllerImpl");
+ mHandlerThread.start();
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
updateNextAlarm();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a906..6a6efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -157,7 +157,7 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
onUserSwitched(mUserTracker.getUserId());
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 2ed9d15..0bc0e88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -36,9 +36,9 @@
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
@@ -66,11 +66,11 @@
/**
*/
@Inject
- public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+ public UserInfoControllerImpl(Context context, @Background Executor bgExecutor,
UserTracker userTracker) {
mContext = context;
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, bgExecutor);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b0..f0b4930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,6 +82,7 @@
private volatile int mZenMode;
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
+ private HandlerThread mHandlerThread;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -133,6 +135,8 @@
}
}
};
+ mHandlerThread = new HandlerThread("ZenModeControllerImpl");
+ mHandlerThread.start();
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
updateZenMode(getModeSettingValueFromProvider());
@@ -143,7 +147,8 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
// This registers the alarm broadcast receiver for the current user
mUserChangedCallback.onUserChanged(getCurrentUser(), context);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..77518db 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -480,7 +480,7 @@
return;
}
- mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 550a65c..f5b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -26,6 +26,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
@@ -38,11 +39,11 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.DejankUtils;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -98,6 +99,7 @@
private UserTracker.Callback mCurrentUserTracker;
private UserTracker mUserTracker;
private final ComponentName mTunerComponent;
+ private HandlerThread mHandlerThread;
/**
*/
@@ -117,7 +119,8 @@
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mTunerComponent = new ComponentName(mContext, TunerActivity.class);
-
+ mHandlerThread = new HandlerThread("TunerServiceImpl");
+ mHandlerThread.start();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
mCurrentUser = user.getUserHandle().getIdentifier();
if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
@@ -135,7 +138,7 @@
}
};
mUserTracker.addCallback(mCurrentUserTracker,
- new HandlerExecutor(mainHandler));
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d..74e1339 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -190,7 +190,7 @@
}
}
- tracker.addCallback(callback, mainDispatcher.asExecutor())
+ tracker.addCallback(callback, backgroundDispatcher.asExecutor())
send(currentSelectionStatus)
awaitClose { tracker.removeCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b127c51..ce6d740 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -50,7 +50,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -77,7 +76,6 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.InputFilter;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -124,7 +122,6 @@
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -138,6 +135,8 @@
import com.android.systemui.util.AlphaTintDrawableWrapper;
import com.android.systemui.util.RoundedCornerProgressDrawable;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
import dagger.Lazy;
@@ -271,9 +270,9 @@
private final Accessibility mAccessibility = new Accessibility();
private final ConfigurationController mConfigurationController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
- private final VolumePanelFactory mVolumePanelFactory;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
- private final ActivityStarter mActivityStarter;
+ private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
+ private final VolumeNavigator mVolumeNavigator;
private boolean mShowing;
private boolean mShowA11yStream;
private int mActiveStream;
@@ -324,9 +323,9 @@
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
MediaOutputDialogFactory mediaOutputDialogFactory,
- VolumePanelFactory volumePanelFactory,
- ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
+ VolumePanelNavigationInteractor volumePanelNavigationInteractor,
+ VolumeNavigator volumeNavigator,
boolean shouldListenForJank,
CsdWarningDialog.Factory csdWarningDialogFactory,
DevicePostureController devicePostureController,
@@ -352,9 +351,7 @@
mDeviceProvisionedController = deviceProvisionedController;
mConfigurationController = configurationController;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
- mVolumePanelFactory = volumePanelFactory;
mCsdWarningDialogFactory = csdWarningDialogFactory;
- mActivityStarter = activityStarter;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
@@ -369,6 +366,8 @@
mUseBackgroundBlur =
mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
mInteractionJankMonitor = interactionJankMonitor;
+ mVolumePanelNavigationInteractor = volumePanelNavigationInteractor;
+ mVolumeNavigator = volumeNavigator;
mSecureSettings = secureSettings;
mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
@@ -1208,13 +1207,8 @@
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
mMediaOutputDialogFactory.dismiss();
- if (FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI)) {
- mVolumePanelFactory.create(true /* aboveStatusBar */, null);
- } else {
- mActivityStarter.startActivity(new Intent(Settings.Panel.ACTION_VOLUME),
- true /* dismissShade */);
- }
+ mVolumeNavigator.openVolumePanel(
+ mVolumePanelNavigationInteractor.getVolumePanelRoute());
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index f180a94..b1bfbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger;
+import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
@@ -26,7 +27,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -40,10 +40,12 @@
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
-import com.android.systemui.volume.VolumePanelFactory;
import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory;
+import com.android.systemui.volume.panel.ui.activity.VolumePanelActivity;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
import dagger.Binds;
import dagger.Lazy;
@@ -78,6 +80,12 @@
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
+ /** Inject into VolumePanelActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumePanelActivity.class)
+ Activity bindVolumePanelActivity(VolumePanelActivity activity);
+
/** */
@Binds
VolumePanelComponentFactory bindVolumePanelComponentFactory(VolumePanelComponent.Factory impl);
@@ -91,9 +99,9 @@
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
MediaOutputDialogFactory mediaOutputDialogFactory,
- VolumePanelFactory volumePanelFactory,
- ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
+ VolumePanelNavigationInteractor volumePanelNavigationInteractor,
+ VolumeNavigator volumeNavigator,
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
DumpManager dumpManager,
@@ -109,9 +117,9 @@
deviceProvisionedController,
configurationController,
mediaOutputDialogFactory,
- volumePanelFactory,
- activityStarter,
interactionJankMonitor,
+ volumePanelNavigationInteractor,
+ volumeNavigator,
true, /* should listen for jank */
csdFactory,
devicePostureController,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt
new file mode 100644
index 0000000..d64bb03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import android.content.Context
+import android.util.FeatureFlagUtils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import javax.inject.Inject
+
+/** Provides navigation routes for Volume space. */
+class VolumePanelNavigationInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val volumePanelFlag: VolumePanelFlag,
+) {
+
+ fun getVolumePanelRoute(): VolumePanelRoute {
+ return when {
+ volumePanelFlag.canUseNewVolumePanel() -> VolumePanelRoute.COMPOSE_VOLUME_PANEL
+ FeatureFlagUtils.isEnabled(
+ context,
+ FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI
+ ) -> VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL
+ else -> VolumePanelRoute.SETTINGS_VOLUME_PANEL
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
index 22a74d2..c85af15 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.domain.model
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+enum class VolumePanelRoute {
+ COMPOSE_VOLUME_PANEL,
+ SETTINGS_VOLUME_PANEL,
+ SYSTEM_UI_VOLUME_PANEL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
new file mode 100644
index 0000000..8ff2837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Inject
+
+@VolumePanelScope
+class BottomBarViewModel
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+ private val volumePanelViewModel: VolumePanelViewModel,
+) {
+
+ fun onDoneClicked() {
+ volumePanelViewModel.dismissPanel()
+ }
+
+ fun onSettingsClicked() {
+ volumePanelViewModel.dismissPanel()
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ true,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 22a74d2..1a4174a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.panel.component.shared.model
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+
+object VolumePanelComponents {
+
+ const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
index 3660ac1..d1d5390 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -16,8 +16,9 @@
package com.android.systemui.volume.panel.dagger
-import com.android.systemui.volume.panel.VolumePanelComponentKey
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Module
import dagger.multibindings.Multibinds
@@ -28,4 +29,6 @@
interface DefaultMultibindsModule {
@Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
+
+ @Multibinds fun components(): Map<VolumePanelComponentKey, VolumePanelUiComponent>
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index 0a057eb..0f19e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,12 +16,14 @@
package com.android.systemui.volume.panel.dagger
+import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.DomainModule
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.UiModule
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import dagger.BindsInstance
import dagger.Subcomponent
@@ -41,6 +43,7 @@
DomainModule::class,
UiModule::class,
// Components modules
+ BottomBarModule::class,
]
)
interface VolumePanelComponent {
@@ -49,6 +52,8 @@
fun componentsInteractor(): ComponentsInteractor
+ fun componentsFactory(): ComponentsFactory
+
fun componentsLayoutManager(): ComponentsLayoutManager
@Subcomponent.Factory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 7817630..f785eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -16,10 +16,11 @@
package com.android.systemui.volume.panel.domain
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -47,6 +48,10 @@
*/
@Provides
@VolumePanelScope
- fun provideEnabledComponents(): Collection<VolumePanelComponentKey> = setOf()
+ fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
+ return setOf(
+ VolumePanelComponents.BOTTOM_BAR,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
index e5b52ea..5301b00 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
@@ -16,10 +16,10 @@
package com.android.systemui.volume.panel.domain.interactor
-import com.android.systemui.volume.panel.VolumePanelComponentKey
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.domain.model.ComponentModel
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
index 9765713..11a9916 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.volume.panel.domain.model
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
/**
* Represents a current state of the Volume Panel component.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
new file mode 100644
index 0000000..d90a9c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.RefactorFlagUtils
+import javax.inject.Inject
+
+/** Provides a flag to check for the new Compose based Volume Panel availability. */
+class VolumePanelFlag @Inject constructor() {
+
+ /**
+ * Returns true when the new Volume Panel is available and false the otherwise. The new panel
+ * can only be available when [ComposeFacade.isComposeAvailable] is true.
+ */
+ fun canUseNewVolumePanel(): Boolean {
+ return ComposeFacade.isComposeAvailable() && Flags.newVolumePanel()
+ }
+
+ fun assertNewVolumePanel() {
+ require(ComposeFacade.isComposeAvailable())
+ RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
index 22a74d2..4644ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.volume.panel.shared.model
/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
typealias VolumePanelComponentKey = String
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt
new file mode 100644
index 0000000..24de41f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.model
+
+/**
+ * An element of a Volume Panel. This can be a button bar, group of sliders or something else. The
+ * only real implementation is Compose-based and located in `compose/features/`.
+ *
+ * Steps for adding an implementation in SystemUI:
+ * 1) Implement `ComposeVolumePanelUiComponent` in `compose/features/`
+ * 2) Add a module binding `ComposeVolumePanelUiComponent` into a map in compose/facade/enabled
+ * 3) Add an interface with the same name as the 2-step module in compose/facade/disabled to stub it
+ * when the Compose is disabled
+ * 4) Add the module to the VolumePanelComponent
+ */
+interface VolumePanelUiComponent
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index bfa7ef2..1346c54 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -16,8 +16,8 @@
package com.android.systemui.volume.panel.ui
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
-import com.android.systemui.volume.panel.ui.viewmodel.DefaultComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
import dagger.Binds
import dagger.Module
@@ -25,5 +25,6 @@
@Module
interface UiModule {
- @Binds fun bindSorter(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
+ @Binds
+ fun bindComponentsLayoutManager(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
new file mode 100644
index 0000000..1b2265b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.core.view.WindowCompat
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Inject
+import javax.inject.Provider
+
+class VolumePanelActivity
+@Inject
+constructor(
+ private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>,
+ private val volumePanelFlag: VolumePanelFlag,
+) : ComponentActivity() {
+
+ private val viewModel: VolumePanelViewModel by
+ viewModels(factoryProducer = { volumePanelViewModelFactory.get() })
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+
+ volumePanelFlag.assertNewVolumePanel()
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt
new file mode 100644
index 0000000..db1c121
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import javax.inject.Inject
+import javax.inject.Provider
+
+/** Provides [VolumePanelComponent] implementation for each [VolumePanelComponentKey]. */
+@VolumePanelScope
+class ComponentsFactory
+@Inject
+constructor(
+ private val componentByKey:
+ Map<
+ VolumePanelComponentKey,
+ @JvmSuppressWildcards
+ Provider<@JvmSuppressWildcards VolumePanelUiComponent>
+ >
+) {
+
+ fun createComponent(key: VolumePanelComponentKey): VolumePanelUiComponent {
+ require(componentByKey.containsKey(key)) { "Component for key=$key is not bound." }
+ return componentByKey.getValue(key).get()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
index 5690ac3..25a95d8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.model
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
/** Represents components grouping into the layout. */
data class ComponentsLayout(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
index f45401a..71ca95c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.viewmodel
+package com.android.systemui.volume.panel.ui.layout
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
/**
* Lays out components to [ComponentsLayout], that UI uses to render the Volume Panel.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
new file mode 100644
index 0000000..ff485c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+import javax.inject.Inject
+
+@VolumePanelScope
+class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
+
+ override fun layout(
+ volumePanelState: VolumePanelState,
+ components: Collection<ComponentState>
+ ): ComponentsLayout {
+ val bottomBarKey = VolumePanelComponents.BOTTOM_BAR
+ return ComponentsLayout(
+ components.filter { it.key != bottomBarKey }.sortedBy { it.key },
+ components.find { it.key == bottomBarKey }
+ ?: error(
+ "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
+ "components layout."
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
index 0a226e2..5f4dbfb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.model
+package com.android.systemui.volume.panel.ui.viewmodel
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
/**
- * State of the [VolumePanelComponent].
+ * State of the [VolumePanelComponent]. It has everything the UI layer needs to layout a particular
+ * component.
*
* @property key uniquely identifies this component
* @property component is an inflated component obtained be the View Model
@@ -27,5 +29,6 @@
*/
data class ComponentState(
val key: VolumePanelComponentKey,
+ val component: VolumePanelUiComponent,
val isVisible: Boolean,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
deleted file mode 100644
index cedfaf3..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.ui.viewmodel
-
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
-import javax.inject.Inject
-
-/**
- * Default [ComponentsLayoutManager]. It places [VolumePanelComponents.BOTTOM_BAR] to
- * [ComponentsLayout.bottomBarComponent] and everything else to
- * [ComponentsLayout.contentComponents].
- */
-@VolumePanelScope
-class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
-
- override fun layout(
- volumePanelState: VolumePanelState,
- components: Collection<ComponentState>
- ): ComponentsLayout = TODO("Unimplemented yet")
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
index 399342f..f67db96 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.ui.model
+package com.android.systemui.volume.panel.ui.viewmodel
import android.content.res.Configuration
import android.content.res.Configuration.Orientation
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index dda361a..d87a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -26,9 +26,9 @@
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
@@ -38,9 +38,10 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.update
class VolumePanelViewModel(
resources: Resources,
@@ -56,6 +57,9 @@
private val componentsInteractor: ComponentsInteractor
get() = volumePanelComponent.componentsInteractor()
+ private val componentsFactory: ComponentsFactory
+ get() = volumePanelComponent.componentsFactory()
+
private val componentsLayoutManager: ComponentsLayoutManager
get() = volumePanelComponent.componentsLayoutManager()
@@ -63,20 +67,22 @@
val volumePanelState: StateFlow<VolumePanelState> =
combine(
- configurationController.onConfigChanged.distinctUntilChanged(),
+ configurationController.onConfigChanged
+ .onStart { emit(resources.configuration) }
+ .distinctUntilChanged(),
mutablePanelVisibility,
) { configuration, isVisible ->
VolumePanelState(orientation = configuration.orientation, isVisible = isVisible)
}
.stateIn(
- volumePanelComponent.coroutineScope(),
+ scope,
SharingStarted.Eagerly,
VolumePanelState(
orientation = resources.configuration.orientation,
isVisible = mutablePanelVisibility.value,
),
)
- val mComponentsLayout: Flow<ComponentsLayout> =
+ val componentsLayout: Flow<ComponentsLayout> =
combine(
componentsInteractor.components,
volumePanelState,
@@ -85,19 +91,20 @@
components.map { model ->
ComponentState(
model.key,
+ componentsFactory.createComponent(model.key),
model.isAvailable,
)
}
componentsLayoutManager.layout(scope, componentStates)
}
.shareIn(
- volumePanelComponent.coroutineScope(),
+ scope,
SharingStarted.Eagerly,
replay = 1,
)
fun dismissPanel() {
- scope.launch { mutablePanelVisibility.emit(false) }
+ mutablePanelVisibility.update { false }
}
override fun onCleared() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
new file mode 100644
index 0000000..790638c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.ui.navigation
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.VolumePanelFactory
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.ui.activity.VolumePanelActivity
+import javax.inject.Inject
+
+class VolumeNavigator
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val volumePanelFactory: VolumePanelFactory,
+ private val activityStarter: ActivityStarter,
+) {
+
+ fun openVolumePanel(route: VolumePanelRoute) {
+ when (route) {
+ VolumePanelRoute.COMPOSE_VOLUME_PANEL ->
+ activityStarter.startActivityDismissingKeyguard(
+ /* intent = */ Intent(context, VolumePanelActivity::class.java),
+ /* onlyProvisioned = */ false,
+ /* dismissShade= */ true,
+ /* disallowEnterPictureInPictureWhileLaunching = */ true,
+ /* callback= */ null,
+ /* flags= */ 0,
+ /* animationController= */ null,
+ /* userHandle= */ null,
+ )
+ VolumePanelRoute.SETTINGS_VOLUME_PANEL ->
+ activityStarter.startActivity(
+ /* intent= */ Intent(Settings.Panel.ACTION_VOLUME),
+ /* dismissShade= */ true
+ )
+ VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL ->
+ volumePanelFactory.create(aboveStatusBar = true, view = null)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..7c6ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -32,6 +32,8 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
@@ -125,6 +127,7 @@
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
+ private HandlerThread mHandlerThread;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
// avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
@@ -206,6 +209,8 @@
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
+ mHandlerThread = new HandlerThread("WMShell");
+ mHandlerThread.start();
}
@Override
@@ -219,7 +224,8 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
// Subscribe to user changes
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d06457b..be06cc5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -121,7 +121,6 @@
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -930,7 +929,8 @@
@Test
public void trustAgentHasTrust() {
// WHEN user has trust
- givenSelectedUserCanSkipBouncerFromTrustedState();
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
// THEN user is considered as "having trust" and bouncer can be skipped
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -954,7 +954,8 @@
@Test
public void trustAgentHasTrust_fingerprintLockout() {
// GIVEN user has trust
- givenSelectedUserCanSkipBouncerFromTrustedState();
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
mSelectedUserInteractor.getSelectedUserId()));
@@ -2015,43 +2016,6 @@
}
@Test
- public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- // GIVEN the user can skip the bouncer
- givenSelectedUserCanSkipBouncerFromTrustedState();
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
-
- // WHEN verify authenticate runs
- verifyFingerprintAuthenticateCall();
- }
-
- @Test
- public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
- mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- // GIVEN the user can skip the bouncer
- givenSelectedUserCanSkipBouncerFromTrustedState();
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
-
- // WHEN verify detect runs
- verifyFingerprintDetectCall();
- }
-
- @Test
public void testFingerprintSensorProperties() throws RemoteException {
mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index e0c6bba..0ba9abe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -32,12 +32,16 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
@@ -59,7 +63,6 @@
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
@@ -76,7 +79,8 @@
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class BackActionInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
private val executor = FakeExecutor(FakeSystemClock())
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -105,6 +109,8 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 54dbd04..3603c3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -63,14 +63,20 @@
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -107,6 +113,8 @@
@RunWith(JUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SideFpsOverlayViewBinderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var displayManager: DisplayManager
@@ -237,7 +245,8 @@
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
- mock(),
+ kosmos.biometricSettingsRepository,
+ kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
@@ -246,10 +255,12 @@
mContext,
mock(),
sfpsSensorInteractor,
- mock(),
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
displayStateInteractor,
UnconfinedTestDispatcher(),
testScope.backgroundScope,
+ kosmos.powerInteractor,
)
viewModel =
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 3888f2b..8127bb1 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
@@ -21,6 +21,7 @@
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
@@ -1244,6 +1245,7 @@
@Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@@ -1251,6 +1253,7 @@
@Test
fun logoResSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@@ -1258,6 +1261,7 @@
@Test
fun logoBitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 1fa60fc..3c43031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -56,19 +56,27 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,6 +106,7 @@
@SmallTest
@RunWith(JUnit4::class)
class SideFpsOverlayViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@@ -239,19 +248,22 @@
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
- mock(),
+ kosmos.biometricSettingsRepository,
+ kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
- mock(),
+ kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
- mock(),
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
displayStateInteractor,
- StandardTestDispatcher(),
+ kosmos.testDispatcher,
testScope.backgroundScope,
+ kosmos.powerInteractor,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 892be35..4f3a63d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -244,6 +244,7 @@
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
)
.apply { start() }
@@ -269,6 +270,7 @@
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
)
.apply { start() }
@@ -906,6 +908,37 @@
}
@Test
+ fun goneToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GONE
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // WHEN the keyguard starts to show
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GONE)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun alternateBouncerToPrimaryBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1269,6 +1302,43 @@
}
@Test
+ fun occludedToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to GLANCEABLE_HUB should occur
+ assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun occludedToAlternateBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to OCCLUDED
@@ -1764,6 +1834,63 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun glanceableHubToOccluded() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
+ runCurrent()
+
+ // GIVEN the device is idle on the glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+
+ // WHEN the keyguard is occluded
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun glanceableHubToGone() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+ // WHEN keyguard goes away
+ keyguardRepository.setKeyguardGoingAway(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 57b5559..acb6ff0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -19,12 +19,15 @@
import android.content.pm.PackageManager
import android.content.res.Resources
+import android.view.View.GONE
+import android.view.View.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
@@ -49,8 +52,11 @@
@Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+ private val bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(VISIBLE)
private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ private val isAodIconsVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
private lateinit var underTest: ClockSection
@@ -110,6 +116,8 @@
mContext.setMockPackageManager(packageManager)
whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+ whenever(keyguardClockViewModel.isAodIconsVisible).thenReturn(isAodIconsVisible)
+ whenever(smartspaceViewModel.bcSmartspaceVisibility).thenReturn(bcSmartspaceVisibility)
underTest =
ClockSection(
@@ -117,6 +125,7 @@
keyguardClockViewModel,
mContext,
splitShadeStateController,
+ smartspaceViewModel,
blueprintInteractor
)
}
@@ -176,6 +185,40 @@
assetSmallClockTop(cs, expectedSmallClockTopMargin)
}
+ @Test
+ fun testSmartspaceVisible_weatherClockDateAndIconsBarrierBottomBelowBCSmartspace() {
+ isAodIconsVisible.value = false
+ bcSmartspaceVisibility.value = VISIBLE
+ val cs = ConstraintSet()
+ underTest.applyDefaultConstraints(cs)
+ val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+ referencedIds.contentEquals(intArrayOf(com.android.systemui.shared.R.id.bc_smartspace_view))
+ }
+
+ @Test
+ fun testSmartspaceGone_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() {
+ isAodIconsVisible.value = false
+ bcSmartspaceVisibility.value = GONE
+ val cs = ConstraintSet()
+ underTest.applyDefaultConstraints(cs)
+ val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+ referencedIds.contentEquals(intArrayOf(R.id.lockscreen_clock_view))
+ }
+
+ @Test
+ fun testHasAodIcons_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() {
+ isAodIconsVisible.value = true
+ val cs = ConstraintSet()
+ underTest.applyDefaultConstraints(cs)
+ val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+ referencedIds.contentEquals(
+ intArrayOf(
+ com.android.systemui.shared.R.id.bc_smartspace_view,
+ R.id.aod_notification_icon_container
+ )
+ )
+ }
+
private fun setLargeClock(useLargeClock: Boolean) {
whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 1b4573d..22a2e93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -34,12 +34,14 @@
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
@@ -68,6 +70,8 @@
@Mock private lateinit var clockFaceConfig: ClockFaceConfig
@Mock private lateinit var eventController: ClockEventController
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor
+ @Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean>
@Before
fun setup() {
@@ -90,12 +94,15 @@
scope.backgroundScope
)
keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository)
+ whenever(notifsKeyguardInteractor.areNotificationsFullyHidden)
+ .thenReturn(areNotificationsFullyHidden)
underTest =
KeyguardClockViewModel(
keyguardInteractor,
keyguardClockInteractor,
scope.backgroundScope,
splitShadeStateController,
+ notifsKeyguardInteractor
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index e4432f3..0636831 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -30,13 +30,11 @@
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
-import android.view.View
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.animation.LaunchableView
import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.logging.PrivacyLogger
@@ -206,10 +204,7 @@
@Test
fun testShowDialogShowsDialogWithView() {
val parent = LinearLayout(context)
- val view =
- object : View(context), LaunchableView {
- override fun setShouldBlockVisibilityChanges(block: Boolean) {}
- }
+ val view = OngoingPrivacyChip(context)
parent.addView(view)
val usage = createMockPermGroupUsage()
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index fa02e8c..f98b68f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -173,7 +173,7 @@
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
@@ -186,7 +186,7 @@
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
@@ -207,7 +207,7 @@
captor.value.onClick(privacyChip)
verify(privacyDialogController, never()).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index b24b877..c0ef50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1069,6 +1069,22 @@
assertThat(mInternetDialogController.mCallback).isNull();
}
+ @Test
+ public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+ }
+
+ @Test
+ public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 45f6869..a6e240b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -22,6 +22,7 @@
import android.testing.ViewUtils
import android.view.MotionEvent
import android.view.View
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -61,6 +62,7 @@
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var powerManager: PowerManager
+ private lateinit var parentView: FrameLayout
private lateinit var containerView: View
private lateinit var testableLooper: TestableLooper
@@ -225,15 +227,38 @@
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
+ @Test
+ fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
+ // Communal is open.
+ communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+ initAndAttachContainerView()
+ testableLooper.processAllMessages()
+
+ // Touch events are intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+
+ // Container view disposed.
+ underTest.disposeView()
+
+ // Touch events are not intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ }
+
private fun initAndAttachContainerView() {
containerView = View(context)
+
+ parentView = FrameLayout(context)
+ parentView.addView(containerView)
+
// Make view clickable so that dispatchTouchEvent returns true.
containerView.isClickable = true
underTest.initView(containerView)
// Attach the view so that flows start collecting.
- ViewUtils.attachView(containerView)
+ ViewUtils.attachView(parentView)
// Give the view a size so that determining if a touch starts at the right edge works.
+ parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index a11839c..6681cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -74,10 +74,12 @@
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -480,6 +482,7 @@
}
@Test
+ @Ignore("b/321332798")
fun setsUpCommunalHubLayout_whenFlagEnabled() {
if (!isComposeAvailable()) {
return
@@ -511,6 +514,8 @@
}
whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
+ whenever(mGlanceableHubContainerController.enabledState())
+ .thenReturn(MutableStateFlow(false))
val mockCommunalPlaceholder = mock(View::class.java)
val fakeViewIndex = 20
@@ -520,8 +525,7 @@
underTest.setupCommunalHubLayout()
- // No adding or removing of views occurs.
- verify(view, times(0)).removeView(mockCommunalPlaceholder)
+ // No adding of views occurs.
verify(view, times(0)).addView(any(), eq(fakeViewIndex))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index c4911a4..cc79ca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,15 +22,18 @@
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
-import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -64,6 +67,8 @@
private val executor = FakeExecutor(FakeSystemClock())
private val testDispatcher = StandardTestDispatcher()
private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,12 +89,14 @@
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
WindowRootViewVisibilityInteractor(
- TestScopeProvider.getTestScope(),
+ testScope,
WindowRootViewVisibilityRepository(iStatusBarService, executor),
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
- ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 65697b73..36f643a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,7 +24,6 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -53,8 +52,8 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -78,28 +77,22 @@
private lateinit var coordinator: ConversationCoordinator
- private val featureFlags = FakeFeatureFlagsClassic()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- coordinator = ConversationCoordinator(
- peopleNotificationIdentifier,
- conversationIconManager,
- HighPriorityProvider(
+ coordinator =
+ ConversationCoordinator(
peopleNotificationIdentifier,
- GroupMembershipManagerImpl(featureFlags)
- ),
- headerController
- )
+ conversationIconManager,
+ HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
+ headerController
+ )
whenever(channel.isImportantConversation).thenReturn(true)
coordinator.attach(pipeline)
// capture arguments:
- promoter = withArgCaptor {
- verify(pipeline).addPromoter(capture())
- }
+ promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) }
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
@@ -111,10 +104,10 @@
entry = NotificationEntryBuilder().setChannel(channel).build()
val section = NotifSection(peopleAlertingSectioner, 0)
- entryA = NotificationEntryBuilder().setChannel(channel)
- .setSection(section).setTag("A").build()
- entryB = NotificationEntryBuilder().setChannel(channel)
- .setSection(section).setTag("B").build()
+ entryA =
+ NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
+ entryB =
+ NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
}
@Test
@@ -129,11 +122,12 @@
val altChildA = NotificationEntryBuilder().setTag("A").build()
val altChildB = NotificationEntryBuilder().setTag("B").build()
val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
- val groupEntry = GroupEntryBuilder()
- .setParent(GroupEntry.ROOT_ENTRY)
- .setSummary(summary)
- .setChildren(listOf(entry, altChildA, altChildB))
- .build()
+ val groupEntry =
+ GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(listOf(entry, altChildA, altChildB))
+ .build()
assertTrue(promoter.shouldPromoteToTopLevel(entry))
assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
@@ -146,41 +140,42 @@
@Test
fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
// GIVEN
- val alertingEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_DEFAULT).build()
+ val alertingEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// put alerting people notifications in this section
assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
- }
+ }
@Test
fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
- val silentEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_LOW).build()
+ val silentEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// THEN put silent people notifications in this section
assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
// People Alerting sectioning happens before the silent one.
- // It claims high important conversations and rest of conversations will be considered as silent.
+ // It claims high important conversations and rest of conversations will be considered as
+ // silent.
assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
}
@Test
fun testNotInPeopleSection() {
// GIVEN
- val entry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_LOW).build()
- val importantEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_HIGH).build()
+ val entry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+ val importantEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
- .thenReturn(TYPE_NON_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
- .thenReturn(TYPE_NON_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
// THEN - only put people notification either silent or alerting
assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
@@ -190,19 +185,23 @@
@Test
fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
// GIVEN
- val altChildA = NotificationEntryBuilder().setTag("A")
- .setImportance(IMPORTANCE_DEFAULT).build()
- val altChildB = NotificationEntryBuilder().setTag("B")
- .setImportance(IMPORTANCE_LOW).build()
- val summary = NotificationEntryBuilder().setId(2)
- .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
- val groupEntry = GroupEntryBuilder()
+ val altChildA =
+ NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
+ val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
+ val summary =
+ NotificationEntryBuilder()
+ .setId(2)
+ .setImportance(IMPORTANCE_LOW)
+ .setChannel(channel)
+ .build()
+ val groupEntry =
+ GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
.setChildren(listOf(altChildA, altChildB))
.build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// THEN
assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
deleted file mode 100644
index c1ffa64..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class GroupMembershipManagerTest : SysuiTestCase() {
- private lateinit var gmm: GroupMembershipManagerImpl
-
- private val featureFlags = FakeFeatureFlagsClassic()
-
- @Before
- fun setUp() {
- gmm = GroupMembershipManagerImpl(featureFlags)
- }
-
- @Test
- fun testIsChildInGroup_topLevel() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse()
- }
-
- @Test
- fun testIsChildInGroup_noParent_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isTrue()
- }
-
- @Test
- fun testIsChildInGroup_noParent_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
- }
- @Test
- fun testIsChildInGroup_summary_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isTrue()
- }
-
- @Test
- fun testIsChildInGroup_summary_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isFalse()
- }
-
- @Test
- fun testIsChildInGroup_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val childEntry = NotificationEntryBuilder().build()
- assertThat(gmm.isChildInGroup(childEntry)).isTrue()
- }
-
- @Test
- fun testIsGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isGroupSummary(entry)).isFalse()
- }
-
- @Test
- fun testIsGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isGroupSummary(summary)).isTrue()
- }
-
- @Test
- fun testIsGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
- assertThat(gmm.isGroupSummary(entry)).isFalse()
- }
-
- @Test
- fun testGetGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.getGroupSummary(entry)).isNull()
- }
-
- @Test
- fun testGetGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
- }
-
- @Test
- fun testGetGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
- assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index ff02ef3..b01281c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -42,9 +42,9 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
@@ -110,7 +110,8 @@
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
new NotificationPanelLoggerFake();
- private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
@@ -133,7 +134,9 @@
mKeyguardRepository,
mHeadsUpManager,
mPowerInteractor,
- mActiveNotificationsInteractor);
+ mActiveNotificationsInteractor,
+ mKosmos.getFakeSceneContainerFlags(),
+ () -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index 3f7fc97..fd41921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -62,7 +62,7 @@
fun onCreateView_noMatchingViewForName_returnNull() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate an ImageView for the expanded layout
val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)
@@ -78,7 +78,7 @@
fun onCreateView_noMatchingViewForLayoutType_returnNull() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate a TextView for the heads-up layout
val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -94,7 +94,7 @@
fun onCreateView_matchingViews_returnReplacementView() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate a TextView for the expanded layout
val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -110,7 +110,7 @@
// GIVEN we have two factories that replaces TextViews in expanded layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
inflaterFactory =
- NotifLayoutInflaterFactory(
+ createNotifLayoutInflaterFactory(
row,
layoutType,
setOf(
@@ -147,4 +147,18 @@
null
}
}
+
+ private fun createNotifLayoutInflaterFactory(
+ row: ExpandableNotificationRow,
+ layoutType: Int,
+ notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory>
+ ) =
+ NotifLayoutInflaterFactory(
+ row,
+ layoutType,
+ object : NotifRemoteViewsFactoryContainer {
+ override val factories: Set<NotifRemoteViewsFactory> =
+ notifRemoteViewsFactoryContainer
+ }
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 71613ed..6549193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -69,9 +69,9 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -124,7 +124,8 @@
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
- private TestScope mTestScope = TestScopeProvider.getTestScope();
+ private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private TestScope mTestScope = mKosmos.getTestScope();
private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
@@ -182,7 +183,10 @@
new FakeKeyguardRepository(),
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
- mActiveNotificationsInteractor);
+ mActiveNotificationsInteractor,
+ mKosmos.getFakeSceneContainerFlags(),
+ () -> mKosmos.getSceneInteractor()
+ );
mGutsManager = new NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
new file mode 100644
index 0000000..446b9d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.content.pm.launcherApps
+import android.graphics.Color
+import android.os.Binder
+import android.os.Handler
+import android.os.userManager
+import android.provider.Settings
+import android.service.notification.NotificationListenerService.Ranking
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.accessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.shadeControllerSceneImpl
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import kotlin.test.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.invocation.InvocationOnMock
+
+/** Tests for [NotificationGutsManager] with the scene container enabled. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
+ private val testNotificationChannel =
+ NotificationChannel(
+ TEST_CHANNEL_ID,
+ TEST_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var handler: Handler
+ private lateinit var helper: NotificationTestHelper
+ private lateinit var gutsManager: NotificationGutsManager
+ private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+ private val metricsLogger = kosmos.metricsLogger
+ private val deviceProvisionedController = kosmos.deviceProvisionedController
+ private val accessibilityManager = kosmos.accessibilityManager
+ private val mBarService = kosmos.statusBarService
+ private val launcherApps = kosmos.launcherApps
+ private val shadeController = kosmos.shadeControllerSceneImpl
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+ private val statusBarStateController = kosmos.statusBarStateController
+ private val headsUpManager = kosmos.headsUpManager
+ private val activityStarter = kosmos.activityStarter
+ private val userManager = kosmos.userManager
+ private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+
+ @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+ @Mock private lateinit var presenter: NotificationPresenter
+ @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+ @Mock private lateinit var notificationListContainer: NotificationListContainer
+ @Mock
+ private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+ @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+ @Mock private lateinit var notificationManager: INotificationManager
+ @Mock private lateinit var shortcutManager: ShortcutManager
+ @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+ @Mock private lateinit var contextTracker: UserContextProvider
+ @Mock private lateinit var bubblesManager: BubblesManager
+ @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+ @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+ sceneContainerFlags.enabled = true
+ testableLooper = TestableLooper.get(this)
+ allowTestableLooperAsMainThread()
+ handler = Handler.createAsync(testableLooper.getLooper())
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+ windowRootViewVisibilityInteractor =
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ WindowRootViewVisibilityRepository(mBarService, executor),
+ FakeKeyguardRepository(),
+ headsUpManager,
+ create().powerInteractor,
+ activeNotificationsInteractor,
+ sceneContainerFlags,
+ { sceneInteractor },
+ )
+ gutsManager =
+ NotificationGutsManager(
+ mContext,
+ handler,
+ handler,
+ javaAdapter,
+ accessibilityManager,
+ highPriorityProvider,
+ notificationManager,
+ userManager,
+ peopleSpaceWidgetManager,
+ launcherApps,
+ shortcutManager,
+ channelEditorDialogController,
+ contextTracker,
+ assistantFeedbackController,
+ Optional.of(bubblesManager),
+ UiEventLoggerFake(),
+ onUserInteractionCallback,
+ shadeController,
+ windowRootViewVisibilityInteractor,
+ notificationLockscreenUserManager,
+ statusBarStateController,
+ mBarService,
+ deviceProvisionedController,
+ metricsLogger,
+ headsUpManager,
+ activityStarter
+ )
+ gutsManager.setUpWithPresenter(
+ presenter,
+ notificationListContainer,
+ onSettingsClickListener
+ )
+ gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+ gutsManager.start()
+ }
+
+ @Test
+ fun testOpenAndCloseGuts() {
+ val guts = Mockito.spy(NotificationGuts(mContext))
+ Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+ ->
+ handler.post((invocation.arguments[0] as Runnable))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ Mockito.doNothing()
+ .`when`(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+ val row = Mockito.spy(realRow)
+ Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+ Mockito.`when`(row.guts).thenReturn(guts)
+ Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+ testableLooper.processAllMessages()
+ verify(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ verify(headsUpManager).setGutsShown(realRow!!.entry, true)
+ assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+ gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+ verify(guts)
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+ verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+ testableLooper.processAllMessages()
+ verify(headsUpManager).setGutsShown(realRow.entry, false)
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_visible_gutsNotClosed() {
+ // First, start out lockscreen or shade as not visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+ val guts = Mockito.mock(NotificationGuts::class.java)
+ gutsManager.exposedGuts = guts
+
+ // WHEN the lockscreen or shade becomes visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the guts are not closed
+ verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
+ verify(guts, Mockito.never())
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_notVisible_gutsClosed() {
+ // First, start out lockscreen or shade as visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+ val guts = Mockito.mock(NotificationGuts::class.java)
+ gutsManager.exposedGuts = guts
+
+ // WHEN the lockscreen or shade is no longer visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the guts are closed
+ verify(guts).removeCallbacks(ArgumentMatchers.any())
+ verify(guts)
+ .closeControls(
+ /* leavebehinds= */ ArgumentMatchers.eq(true),
+ /* controls= */ ArgumentMatchers.eq(true),
+ /* x= */ ArgumentMatchers.anyInt(),
+ /* y= */ ArgumentMatchers.anyInt(),
+ /* force= */ ArgumentMatchers.eq(true)
+ )
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_notVisible_listContainerReset() {
+ // First, start out lockscreen or shade as visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+
+ // WHEN the lockscreen or shade is no longer visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the list container is reset
+ verify(notificationListContainer)
+ .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ fun testChangeDensityOrFontScale() {
+ val guts = Mockito.spy(NotificationGuts(mContext))
+ Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+ ->
+ handler.post((invocation.arguments[0] as Runnable))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ Mockito.doNothing()
+ .`when`(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+ val row = Mockito.spy(realRow)
+ Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+ Mockito.`when`(row.guts).thenReturn(guts)
+ Mockito.doNothing().`when`(row).ensureGutsInflated()
+ val realEntry = realRow!!.entry
+ val entry = Mockito.spy(realEntry)
+ Mockito.`when`(entry.row).thenReturn(row)
+ Mockito.`when`(entry.getGuts()).thenReturn(guts)
+ Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ testableLooper.processAllMessages()
+ verify(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+
+ // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+ verify(row).setGutsView(ArgumentMatchers.any())
+ row.onDensityOrFontScaleChanged()
+ gutsManager.onDensityOrFontScaleChanged(entry)
+ testableLooper.processAllMessages()
+ gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+ verify(guts)
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+
+ // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+ verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_mic() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_mic() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_mic_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_mic_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_highPriority() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ val entry = row.entry
+ NotificationEntryHelper.modifyRanking(entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .setImportance(NotificationManager.IMPORTANCE_HIGH)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+ val statusBarNotification = entry.sbn
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(true), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ val statusBarNotification = row.entry.sbn
+ val entry = row.entry
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(false), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_withInitialAction() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ val statusBarNotification = row.entry.sbn
+ val entry = row.entry
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(false), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ private fun createTestNotificationRow(): ExpandableNotificationRow? {
+ val nb =
+ Notification.Builder(mContext, testNotificationChannel.id)
+ .setContentTitle("foo")
+ .setColorized(true)
+ .setColor(Color.RED)
+ .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setSmallIcon(R.drawable.sym_def_app_icon)
+ return try {
+ val row = helper.createRow(nb.build())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setChannel(testNotificationChannel)
+ .build()
+ row
+ } catch (e: Exception) {
+ org.junit.Assert.fail()
+ null
+ }
+ }
+
+ private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) {
+ val key =
+ if (isVisible) {
+ SceneKey.Lockscreen
+ } else {
+ SceneKey.Bouncer
+ }
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+
+ private fun createTestMenuItem(
+ row: ExpandableNotificationRow?
+ ): NotificationMenuRowPlugin.MenuItem {
+ val menuRow: NotificationMenuRowPlugin =
+ NotificationMenuRow(mContext, peopleNotificationIdentifier)
+ menuRow.createMenu(row, row!!.entry.sbn)
+ val menuItem = menuRow.getLongpressMenuItem(mContext)
+ Assert.assertNotNull(menuItem)
+ return menuItem
+ }
+
+ companion object {
+ private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 20d5c5d..49953a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.platform.test.annotations.EnableFlags
import android.telephony.CellSignalStrength
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
@@ -159,10 +160,13 @@
}
@Test
- fun numberOfLevels_comesFromRepo() =
+ fun numberOfLevels_comesFromRepo_whenApplicable() =
testScope.runTest {
var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this)
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels }
+ .launchIn(this)
connectionRepository.numberOfLevels.value = 5
assertThat(latest).isEqualTo(5)
@@ -491,14 +495,19 @@
}
@Test
- fun iconId_correctLevel_notCutout() =
+ fun cellBasedIconId_correctLevel_notCutout() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
connectionRepository.primaryLevel.value = 1
connectionRepository.setDataEnabled(false)
+ connectionRepository.isNonTerrestrial.value = false
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest?.level).isEqualTo(1)
assertThat(latest?.showExclamationMark).isFalse()
@@ -509,6 +518,7 @@
@Test
fun icon_usesLevelFromInteractor() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
var latest: SignalIconModel? = null
@@ -524,10 +534,15 @@
}
@Test
- fun icon_usesNumberOfLevelsFromInteractor() =
+ fun cellBasedIcon_usesNumberOfLevelsFromInteractor() =
testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ connectionRepository.isNonTerrestrial.value = false
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
connectionRepository.numberOfLevels.value = 5
assertThat(latest!!.numberOfLevels).isEqualTo(5)
@@ -539,12 +554,16 @@
}
@Test
- fun icon_defaultDataDisabled_showExclamationTrue() =
+ fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest!!.showExclamationMark).isTrue()
@@ -552,12 +571,16 @@
}
@Test
- fun icon_defaultConnectionFailed_showExclamationTrue() =
+ fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
mobileIconsInteractor.isDefaultConnectionFailed.value = true
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest!!.showExclamationMark).isTrue()
@@ -565,14 +588,18 @@
}
@Test
- fun icon_enabledAndNotFailed_showExclamationFalse() =
+ fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() =
testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
mobileIconsInteractor.isDefaultConnectionFailed.value = false
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
assertThat(latest!!.showExclamationMark).isFalse()
@@ -580,11 +607,15 @@
}
@Test
- fun icon_usesEmptyState_whenNotInService() =
+ fun cellBasedIcon_usesEmptyState_whenNotInService() =
testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = false
assertThat(latest?.level).isEqualTo(0)
@@ -604,11 +635,15 @@
}
@Test
- fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+ fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular? }
+ .launchIn(this)
+ connectionRepository.isNonTerrestrial.value = false
connectionRepository.isInService.value = true
connectionRepository.carrierNetworkChangeActive.value = true
connectionRepository.primaryLevel.value = 1
@@ -626,6 +661,20 @@
job.cancel()
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun satBasedIcon_isUsedWhenNonTerrestrial() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // Start off using cellular
+ assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+
+ connectionRepository.isNonTerrestrial.value = true
+
+ assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
index 90a8946..ebec003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -32,7 +32,7 @@
@Test
fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() {
val model =
- SignalIconModel(
+ SignalIconModel.Cellular(
level = 0,
numberOfLevels = 4,
showExclamationMark = false,
@@ -59,7 +59,7 @@
val expected: Int,
) {
fun toSignalIconModel() =
- SignalIconModel(
+ SignalIconModel.Cellular(
level = level,
numberOfLevels = numberOfLevels,
showExclamationMark = showExclamation,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 889130f..deb9fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -190,7 +190,7 @@
/** Convenience constructor for these tests */
fun defaultSignal(level: Int = 1): SignalIconModel {
- return SignalIconModel(
+ return SignalIconModel.Cellular(
level,
NUM_LEVELS,
showExclamationMark = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 147efcb..83d0fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -55,6 +55,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -709,6 +710,87 @@
.isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun nonTerrestrial_defaultProperties() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+
+ val roaming by collectLastValue(underTest.roaming)
+ val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+ val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+ val activityInVisible by collectLastValue(underTest.activityInVisible)
+ val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+ val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun nonTerrestrial_ignoresDefaultProperties() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+
+ val roaming by collectLastValue(underTest.roaming)
+ val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+ val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+ val activityInVisible by collectLastValue(underTest.activityInVisible)
+ val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+ val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+ repository.setAllRoaming(true)
+ repository.setNetworkTypeKey(connectionsRepository.LTE_KEY)
+ // sets the background on cellular
+ repository.hasPrioritizedNetworkCapabilities.value = true
+ repository.dataActivityDirection.value =
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = true,
+ )
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun nonTerrestrial_usesSatelliteIcon() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+ repository.setAllLevels(0)
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
private fun createAndSetViewModel() {
underTest =
MobileIconViewModel(
@@ -723,24 +805,5 @@
companion object {
private const val SUB_1_ID = 1
- private const val NUM_LEVELS = 4
-
- /** Convenience constructor for these tests */
- fun defaultSignal(level: Int = 1): SignalIconModel {
- return SignalIconModel(
- level,
- NUM_LEVELS,
- showExclamationMark = false,
- carrierNetworkChange = false,
- )
- }
-
- fun emptySignal(): SignalIconModel =
- SignalIconModel(
- level = 0,
- numberOfLevels = NUM_LEVELS,
- showExclamationMark = true,
- carrierNetworkChange = false,
- )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c..457acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -190,7 +190,7 @@
mWakefulnessLifecycle.dispatchFinishedWakingUp();
mThemeOverlayController.start();
- verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
+ verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor));
verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
eq(UserHandle.USER_ALL));
verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d839da1..7a8dce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -70,7 +70,6 @@
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.res.R;
@@ -83,6 +82,8 @@
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
import dagger.Lazy;
@@ -132,10 +133,6 @@
@Mock
MediaOutputDialogFactory mMediaOutputDialogFactory;
@Mock
- VolumePanelFactory mVolumePanelFactory;
- @Mock
- ActivityStarter mActivityStarter;
- @Mock
InteractionJankMonitor mInteractionJankMonitor;
@Mock
private DumpManager mDumpManager;
@@ -144,6 +141,11 @@
DevicePostureController mPostureController;
@Mock
private Lazy<SecureSettings> mLazySecureSettings;
+ @Mock
+ private VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
+ @Mock
+ private VolumeNavigator mVolumeNavigator;
+
private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
new CsdWarningDialog.Factory() {
@Override
@@ -196,9 +198,9 @@
mDeviceProvisionedController,
mConfigurationController,
mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
mInteractionJankMonitor,
+ mVolumePanelNavigationInteractor,
+ mVolumeNavigator,
false,
mCsdWarningDialogFactory,
mPostureController,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index 22a74d2..2a05598 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package android.view
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.windowManager by Kosmos.Fixture<WindowManager> { mock(WindowManager::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
new file mode 100644
index 0000000..8fcb60c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.hardware.biometrics.common.AuthenticateReason
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeFingerprintInteractiveToAuthProvider : FingerprintInteractiveToAuthProvider {
+ override val enabledForCurrentUser: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ private val userIdToExtension = mutableMapOf<Int, AuthenticateReason.Vendor>()
+ override fun getVendorExtension(userId: Int): AuthenticateReason.Vendor? =
+ userIdToExtension[userId]
+
+ fun setVendorExtension(userId: Int, extension: AuthenticateReason.Vendor) {
+ userIdToExtension[userId] = extension
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
new file mode 100644
index 0000000..57dc37e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fingerprintInteractiveToAuthProvider by
+ Kosmos.Fixture { fakeFingerprintInteractiveToAuthProvider }
+
+val Kosmos.fakeFingerprintInteractiveToAuthProvider by
+ Kosmos.Fixture { FakeFingerprintInteractiveToAuthProvider() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
index 8702e00..b5515c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
@@ -19,4 +19,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
+val Kosmos.fingerprintPropertyRepository by Fixture { fakeFingerprintPropertyRepository }
+
+val Kosmos.fakeFingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
new file mode 100644
index 0000000..979a49b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import android.view.windowManager
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.fingerprintInteractiveToAuthProvider
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.SideFpsLogger
+import java.util.Optional
+import org.mockito.Mockito.mock
+
+val Kosmos.sideFpsSensorInteractor by
+ Kosmos.Fixture {
+ SideFpsSensorInteractor(
+ applicationContext,
+ fingerprintPropertyRepository,
+ windowManager,
+ displayStateInteractor,
+ Optional.of(fingerprintInteractiveToAuthProvider),
+ biometricSettingsRepository,
+ keyguardTransitionInteractor,
+ mock(SideFpsLogger::class.java),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 20fa545..cccd908 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -50,4 +50,11 @@
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
+
+ private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
+
+ fun setCommunalEnabledState(enabled: Boolean) {
+ _communalEnabledState.value = enabled
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
index 45d39b0..cf8f812 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
@@ -19,4 +19,5 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.fakeBiometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.biometricSettingsRepository by Fixture { fakeBiometricSettingsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
index 6437ef3..0d20939 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
@@ -19,6 +19,10 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+val Kosmos.fakeDeviceEntryFingerprintAuthRepository by Fixture {
FakeDeviceEntryFingerprintAuthRepository()
}
+
+val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+ fakeDeviceEntryFingerprintAuthRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 5ca0439..4a85909 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.splitShadeStateController
val Kosmos.keyguardClockViewModel by
@@ -29,5 +30,6 @@
keyguardClockInteractor = keyguardClockInteractor,
applicationScope = applicationCoroutineScope,
splitShadeStateController = splitShadeStateController,
+ notifsKeyguardInteractor = notificationsKeyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 7cc5d6b..e13fa52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -14,9 +14,66 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade
+import android.view.WindowManager
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() }
+val Kosmos.shadeControllerSceneImpl by
+ Kosmos.Fixture {
+ ShadeControllerSceneImpl(
+ scope = applicationCoroutineScope,
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
+ notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
+ deviceEntryInteractor = deviceEntryInteractor,
+ touchLog = mock<LogBuffer>(),
+ commandQueue = mock<CommandQueue>(),
+ statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
+ notificationShadeWindowController = mock<NotificationShadeWindowController>(),
+ assistManagerLazy = { mock<AssistManager>() },
+ )
+ }
+
+val Kosmos.shadeControllerImpl by
+ Kosmos.Fixture {
+ ShadeControllerImpl(
+ mock<CommandQueue>(),
+ fakeExecutor,
+ mock<LogBuffer>(),
+ windowRootViewVisibilityInteractor,
+ mock<KeyguardStateController>(),
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ mock<StatusBarWindowController>(),
+ deviceProvisionedController,
+ mock<NotificationShadeWindowController>(),
+ mock<WindowManager>(),
+ { mock<ShadeViewController>() },
+ { mock<AssistManager>() },
+ { mock<NotificationGutsManager>() },
+ )
+ }
+var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index dda7fad..4efcada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -52,32 +52,38 @@
return ge;
}
+ /** Sets the group key. */
public GroupEntryBuilder setKey(String key) {
mKey = key;
return this;
}
+ /** Sets the creation time. */
public GroupEntryBuilder setCreationTime(long creationTime) {
mCreationTime = creationTime;
return this;
}
+ /** Sets the parent entry of the group. */
public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
mParent = entry;
return this;
}
+ /** Sets the section the group belongs to. */
public GroupEntryBuilder setSection(@Nullable NotifSection section) {
mNotifSection = section;
return this;
}
+ /** Sets the group summary. */
public GroupEntryBuilder setSummary(
NotificationEntry summary) {
mSummary = summary;
return this;
}
+ /** Sets the group children. */
public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
mChildren.clear();
mChildren.addAll(children);
@@ -90,6 +96,7 @@
return this;
}
+ /** Get the group's internal children list. */
public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
return groupEntry.getRawChildren();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index d80ee75..cf800d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -23,6 +23,8 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
@@ -34,5 +36,7 @@
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
+ sceneInteractorProvider = { sceneInteractor },
+ sceneContainerFlags = sceneContainerFlags,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
index 22a74d2..370b177 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.statusbar.phone
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.dozeServiceHost: DozeServiceHost by Kosmos.Fixture { mock(DozeServiceHost::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 5f4d7bf..c010327 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -60,6 +60,8 @@
override val isInService = MutableStateFlow(true)
+ override val isNonTerrestrial = MutableStateFlow(false)
+
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
@@ -69,7 +71,7 @@
override val signalLevelIcon: MutableStateFlow<SignalIconModel> =
MutableStateFlow(
- SignalIconModel(
+ SignalIconModel.Cellular(
level = 0,
numberOfLevels = 4,
showExclamationMark = false,
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af5..e33fff1 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@
# Keep all feature flag implementations
class :feature_flags stubclass
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
# Collections
class android.util.ArrayMap stubclass
class android.util.ArraySet stubclass
@@ -112,6 +115,12 @@
class android.os.PatternMatcher stubclass
class android.os.ParcelUuid stubclass
+# Logging related interfaces from modules-utils
+class com.android.internal.logging.InstanceId stubclass
+class com.android.internal.logging.InstanceIdSequence stubclass
+class com.android.internal.logging.UiEvent stubclass
+class com.android.internal.logging.UiEventLogger stubclass
+
# XML
class com.android.internal.util.XmlPullParserWrapper stubclass
class com.android.internal.util.XmlSerializerWrapper stubclass
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f..91c522e 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -19,8 +19,6 @@
import android.os.HandlerThread;
import android.os.Looper;
-import java.util.Objects;
-
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -31,6 +29,10 @@
public static void init(RavenwoodRule rule) {
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
+ android.os.SystemProperties.init$ravenwood(
+ rule.mSystemProperties.getValues(),
+ rule.mSystemProperties.getKeyReadablePredicate(),
+ rule.mSystemProperties.getKeyWritablePredicate());
com.android.server.LocalServices.removeAllServicesForTest();
@@ -49,7 +51,8 @@
com.android.server.LocalServices.removeAllServicesForTest();
- android.os.Process.reset$ravenwood();
+ android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
+ android.os.Process.reset$ravenwood();
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 53da8ba..dd442f0 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -62,6 +62,8 @@
boolean mProvideMainThread = false;
+ final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
public RavenwoodRule() {
}
@@ -98,6 +100,40 @@
return this;
}
+ /**
+ * Configure the given system property as immutable for the duration of the test.
+ * Read access to the key is allowed, and write access will fail. When {@code value} is
+ * {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadOnly(key);
+ return this;
+ }
+
+ /**
+ * Configure the given system property as mutable for the duration of the test.
+ * Both read and write access to the key is allowed, and its value will be reset between
+ * each test. When {@code value} is {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadWrite(key);
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 0000000..85ad4e4
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+ private final Map<String, String> mValues = new HashMap<>();
+
+ /** Set of additional keys that should be considered readable */
+ private final Set<String> mKeyReadable = new HashSet<>();
+ private final Predicate<String> mKeyReadablePredicate = (key) -> {
+ final String root = getKeyRoot(key);
+
+ if (root.startsWith("debug.")) return true;
+
+ // This set is carefully curated to help identify situations where a test may
+ // accidentally depend on a default value of an obscure property whose owner hasn't
+ // decided how Ravenwood should behave.
+ if (root.startsWith("boot.")) return true;
+ if (root.startsWith("build.")) return true;
+ if (root.startsWith("product.")) return true;
+ if (root.startsWith("soc.")) return true;
+ if (root.startsWith("system.")) return true;
+
+ switch (key) {
+ case "gsm.version.baseband":
+ case "no.such.thing":
+ case "ro.bootloader":
+ case "ro.debuggable":
+ case "ro.hardware":
+ case "ro.hw_timeout_multiplier":
+ case "ro.odm.build.media_performance_class":
+ case "ro.treble.enabled":
+ case "ro.vndk.version":
+ return true;
+ }
+
+ return mKeyReadable.contains(key);
+ };
+
+ /** Set of additional keys that should be considered writable */
+ private final Set<String> mKeyWritable = new HashSet<>();
+ private final Predicate<String> mKeyWritablePredicate = (key) -> {
+ final String root = getKeyRoot(key);
+
+ if (root.startsWith("debug.")) return true;
+
+ return mKeyWritable.contains(key);
+ };
+
+ public RavenwoodSystemProperties() {
+ // TODO: load these values from build.prop generated files
+ setValueForPartitions("product.brand", "Android");
+ setValueForPartitions("product.device", "Ravenwood");
+ setValueForPartitions("product.manufacturer", "Android");
+ setValueForPartitions("product.model", "Ravenwood");
+ setValueForPartitions("product.name", "Ravenwood");
+
+ setValueForPartitions("product.cpu.abilist", "x86_64");
+ setValueForPartitions("product.cpu.abilist32", "");
+ setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+ setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+ setValueForPartitions("build.date.utc", "1704092400");
+ setValueForPartitions("build.id", "MAIN");
+ setValueForPartitions("build.tags", "dev-keys");
+ setValueForPartitions("build.type", "userdebug");
+ setValueForPartitions("build.version.all_codenames", "REL");
+ setValueForPartitions("build.version.codename", "REL");
+ setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+ setValueForPartitions("build.version.known_codenames", "REL");
+ setValueForPartitions("build.version.release", "14");
+ setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+ setValueForPartitions("build.version.sdk", "34");
+
+ setValue("ro.board.first_api_level", "1");
+ setValue("ro.product.first_api_level", "1");
+
+ setValue("ro.soc.manufacturer", "Android");
+ setValue("ro.soc.model", "Ravenwood");
+
+ setValue("ro.debuggable", "1");
+ }
+
+ Map<String, String> getValues() {
+ return new HashMap<>(mValues);
+ }
+
+ Predicate<String> getKeyReadablePredicate() {
+ return mKeyReadablePredicate;
+ }
+
+ Predicate<String> getKeyWritablePredicate() {
+ return mKeyWritablePredicate;
+ }
+
+ private static final String[] PARTITIONS = {
+ "bootimage",
+ "odm",
+ "product",
+ "system",
+ "system_ext",
+ "vendor",
+ "vendor_dlkm",
+ };
+
+ /**
+ * Set the given property for all possible partitions where it could be defined. For
+ * example, the value of {@code ro.build.type} is typically also mirrored under
+ * {@code ro.system.build.type}, etc.
+ */
+ private void setValueForPartitions(String key, String value) {
+ setValue("ro." + key, value);
+ for (String partition : PARTITIONS) {
+ setValue("ro." + partition + "." + key, value);
+ }
+ }
+
+ public void setValue(String key, Object value) {
+ final String valueString = (value == null) ? null : String.valueOf(value);
+ if ((valueString == null) || valueString.isEmpty()) {
+ mValues.remove(key);
+ } else {
+ mValues.put(key, valueString);
+ }
+ }
+
+ public void setAccessNone(String key) {
+ mKeyReadable.remove(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadOnly(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadWrite(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.add(key);
+ }
+
+ /**
+ * Return the "root" of the given property key, stripping away any modifier prefix such as
+ * {@code ro.} or {@code persist.}.
+ */
+ private static String getKeyRoot(String key) {
+ if (key.startsWith("ro.")) {
+ return key.substring(3);
+ } else if (key.startsWith("persist.")) {
+ return key.substring(8);
+ } else {
+ return key;
+ }
+ }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index ab2546b..5700f00 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,9 @@
# Only classes listed here can use the Ravenwood annotations.
com.android.internal.util.ArrayUtils
+com.android.internal.logging.MetricsLogger
+com.android.internal.logging.testing.FakeMetricsLogger
+com.android.internal.logging.testing.UiEventLoggerFake
com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistory$TraceDelegate
com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -47,6 +50,7 @@
android.os.Binder
android.os.Binder$IdentitySupplier
android.os.Broadcaster
+android.os.Build
android.os.BundleMerger
android.os.ConditionVariable
android.os.FileUtils
@@ -65,8 +69,10 @@
android.os.Process
android.os.ServiceSpecificException
android.os.SystemClock
+android.os.SystemProperties
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
+android.os.Trace
android.os.UidBatteryConsumer
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
@@ -126,6 +132,8 @@
android.content.ContentProvider
+android.metrics.LogMaker
+
com.android.server.LocalServices
com.android.server.power.stats.BatteryStatsImpl
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 993b254..44682e2 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,6 +45,13 @@
}
flag {
+ name: "fix_drag_pointer_when_ending_drag"
+ namespace: "accessibility"
+ description: "Send the correct pointer id when transitioning from dragging to delegating states."
+ bug: "300002193"
+}
+
+flag {
name: "pinch_zoom_zero_min_span"
namespace: "accessibility"
description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..3086ce1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1466,8 +1466,11 @@
int policyFlags = mState.getLastReceivedPolicyFlags();
if (mState.isDragging()) {
// Send an event to the end of the drag gesture.
- mDispatcher.sendMotionEvent(
- event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags);
+ int pointerIdBits = ALL_POINTER_ID_BITS;
+ if (Flags.fixDragPointerWhenEndingDrag()) {
+ pointerIdBits = 1 << mDraggingPointerId;
+ }
+ mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
}
mState.startDelegating();
// Deliver all pointers to the view hierarchy.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b6e1140..2168cb2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -583,6 +583,11 @@
return associationInfo == null ? null : associationInfo.getDisplayName();
}
+ @Override // Binder call
+ public @NonNull List<String> getAllPersistentDeviceIds() {
+ return new ArrayList<>(mLocalService.getAllPersistentDeviceIds());
+ }
+
// Binder call
@Override
public boolean isValidVirtualDeviceId(int deviceId) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3164e08..a54a48a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -213,6 +213,8 @@
"service-jobscheduler-deviceidle.flags-aconfig-java",
"backup_flags_lib",
"policy_flags_lib",
+ "net_flags_lib",
+ "stats_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9b1fade..afb8345 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4491,6 +4491,12 @@
}
}
if (userId > 0) {
+ if (mAm.isSystemUserOnly(sInfo.flags)) {
+ Slog.w(TAG_SERVICE, service + " is only available for the SYSTEM user,"
+ + " calling userId is: " + userId);
+ return null;
+ }
+
if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
sInfo.name, sInfo.flags)
&& mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 86894fd..ca04e41 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -472,6 +472,8 @@
import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.stats.pull.StatsPullAtomService;
+import com.android.server.stats.pull.StatsPullAtomServiceInternal;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -1308,6 +1310,8 @@
*/
final BatteryStatsService mBatteryStatsService;
+ StatsPullAtomServiceInternal mStatsPullAtomServiceInternal;
+
/**
* Information about component usage
*/
@@ -5087,13 +5091,11 @@
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
- reason);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
null, null, AppOpsManager.OP_NONE,
- bOptions.toBundle(), true,
+ null, true,
false, MY_PID, SYSTEM_UID,
SYSTEM_UID, MY_PID, app.userId);
}
@@ -13763,6 +13765,11 @@
return result;
}
+ boolean isSystemUserOnly(int flags) {
+ return android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+ && (flags & ServiceInfo.FLAG_SYSTEM_USER_ONLY) != 0;
+ }
+
/**
* Checks to see if the caller is in the same app as the singleton
* component, or the component is in a special app. It allows special apps
@@ -16551,6 +16558,21 @@
final @ProcessCapability int capability) {
mBatteryStatsService.noteUidProcessState(uid, state);
mAppOpsService.updateUidProcState(uid, state, capability);
+ if (StatsPullAtomService.ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ try {
+ if (mStatsPullAtomServiceInternal == null) {
+ mStatsPullAtomServiceInternal = LocalServices.getService(
+ StatsPullAtomServiceInternal.class);
+ }
+ if (mStatsPullAtomServiceInternal != null) {
+ mStatsPullAtomServiceInternal.noteUidProcessState(uid, state);
+ } else {
+ Slog.d(TAG, "StatsPullAtomService not ready yet");
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception during logging uid proc state change event", e);
+ }
+ }
if (mTrackingAssociations) {
for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) {
ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 57c52c2..45f657d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3754,6 +3754,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 1f98aba..fb89b8e 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -102,6 +102,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onProcessDied(int pid, int uid) {
}
};
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 095d907..30f21a6 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1249,9 +1249,9 @@
ProviderInfo cpi = providers.get(i);
boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
- if (singleton && app.userId != UserHandle.USER_SYSTEM) {
- // This is a singleton provider, but a user besides the
- // default user is asking to initialize a process it runs
+ if (isSingletonOrSystemUserOnly(cpi) && app.userId != UserHandle.USER_SYSTEM) {
+ // This is a singleton or a SYSTEM user only provider, but a user besides the
+ // SYSTEM user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process,
// it runs in the process of the default user. Get rid of it.
providers.remove(i);
@@ -1398,8 +1398,7 @@
final boolean processMatch =
Objects.equals(pi.processName, app.processName)
|| pi.multiprocess;
- final boolean userMatch = !mService.isSingleton(
- pi.processName, pi.applicationInfo, pi.name, pi.flags)
+ final boolean userMatch = !isSingletonOrSystemUserOnly(pi)
|| app.userId == UserHandle.USER_SYSTEM;
final boolean isInstantApp = pi.applicationInfo.isInstantApp();
final boolean splitInstalled = pi.splitName == null
@@ -1985,4 +1984,13 @@
return isAuthRedirected;
}
}
+
+ /**
+ * Returns true if Provider is either singleUser or systemUserOnly provider.
+ */
+ private boolean isSingletonOrSystemUserOnly(ProviderInfo pi) {
+ return (android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+ && mService.isSystemUserOnly(pi.flags))
+ || mService.isSingleton(pi.processName, pi.applicationInfo, pi.name, pi.flags);
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fa5dbd2..f5c34a5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2852,6 +2852,7 @@
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
}
+ dispatchProcessStarted(app, pid);
checkSlow(app.getStartTime(), "startProcess: done updating pids map");
return true;
}
@@ -4977,6 +4978,22 @@
}
}
+ void dispatchProcessStarted(ProcessRecord app, int pid) {
+ int i = mProcessObservers.beginBroadcast();
+ while (i > 0) {
+ i--;
+ final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+ if (observer != null) {
+ try {
+ observer.onProcessStarted(pid, app.uid, app.info.uid,
+ app.info.packageName, app.processName);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mProcessObservers.finishBroadcast();
+ }
+
void dispatchProcessDied(int pid, int uid) {
int i = mProcessObservers.beginBroadcast();
while (i > 0) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index dc14c7a..7aafda5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -153,6 +153,7 @@
"machine_learning",
"mainline_modularization",
"mainline_sdk",
+ "make_pixel_haptics",
"media_audio",
"media_drm",
"media_reliability",
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 684d6a0..cdd147a 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -177,6 +177,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
};
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 99b45ec..cd295b5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1047,11 +1047,9 @@
private void initAudioHalBluetoothState() {
synchronized (mBluetoothAudioStateLock) {
mBluetoothScoOnApplied = false;
- AudioSystem.setParameters("BT_SCO=off");
mBluetoothA2dpSuspendedApplied = false;
- AudioSystem.setParameters("A2dpSuspended=false");
mBluetoothLeSuspendedApplied = false;
- AudioSystem.setParameters("LeAudioSuspended=false");
+ reapplyAudioHalBluetoothState();
}
}
@@ -1114,6 +1112,34 @@
}
}
+ @GuardedBy("mBluetoothAudioStateLock")
+ private void reapplyAudioHalBluetoothState() {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+ + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+ + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+ + mBluetoothLeSuspendedApplied);
+ }
+ // Note: the order of parameters is important.
+ if (mBluetoothScoOnApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ AudioSystem.setParameters("BT_SCO=on");
+ } else {
+ AudioSystem.setParameters("BT_SCO=off");
+ if (mBluetoothA2dpSuspendedApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ } else {
+ AudioSystem.setParameters("A2dpSuspended=false");
+ }
+ if (mBluetoothLeSuspendedApplied) {
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ } else {
+ AudioSystem.setParameters("LeAudioSuspended=false");
+ }
+ }
+ }
+
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1775,6 +1801,9 @@
initRoutingStrategyIds();
updateActiveCommunicationDevice();
mDeviceInventory.onRestoreDevices();
+ synchronized (mBluetoothAudioStateLock) {
+ reapplyAudioHalBluetoothState();
+ }
mBtHelper.onAudioServerDiedRestoreA2dp();
updateCommunicationRoute("MSG_RESTORE_DEVICES");
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 8fd2ee2..21e6bac 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -439,6 +439,10 @@
if (fingerprintService != null) {
fingerprintService.registerAuthenticationStateListener(listener);
}
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ faceService.registerAuthenticationStateListener(listener);
+ }
}
@Override
@@ -449,6 +453,10 @@
if (fingerprintService != null) {
fingerprintService.unregisterAuthenticationStateListener(listener);
}
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ faceService.unregisterAuthenticationStateListener(listener);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 5863535..1ae4d64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -91,6 +91,40 @@
}
}
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ public void onAuthenticationSucceeded(int requestReason, int userId) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationSucceeded(requestReason, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "succeeded", e);
+ }
+ }
+ }
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ public void onAuthenticationFailed(int requestReason, int userId) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationFailed(requestReason, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "failed", e);
+ }
+ }
+ }
+
@Override
public void binderDied() {
// Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 73f3999..321e951 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
+import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
@@ -63,6 +64,7 @@
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -99,6 +101,8 @@
private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
mBiometricStateCallback;
@NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
+ @NonNull
private final FaceProviderFunction mFaceProviderFunction;
@NonNull private final Function<String, FaceProvider> mFaceProvider;
@NonNull
@@ -695,7 +699,8 @@
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
providers.add(
Face10.newInstance(getContext(), mBiometricStateCallback,
- hidlSensor, mLockoutResetDispatcher));
+ mAuthenticationStateListeners, hidlSensor,
+ mLockoutResetDispatcher));
}
return providers;
@@ -830,6 +835,24 @@
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
mBiometricStateCallback.registerBiometricStateListener(listener);
}
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void registerAuthenticationStateListener(
+ @NonNull AuthenticationStateListener listener) {
+ super.registerAuthenticationStateListener_enforcePermission();
+
+ mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void unregisterAuthenticationStateListener(
+ @NonNull AuthenticationStateListener listener) {
+ super.unregisterAuthenticationStateListener_enforcePermission();
+
+ mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+ }
}
public FaceService(Context context) {
@@ -848,6 +871,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mAuthenticationStateListeners = new AuthenticationStateListeners();
mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
@@ -868,8 +892,8 @@
try {
final SensorProps[] props = face.getSensorProps();
return new FaceProvider(getContext(),
- mBiometricStateCallback, props, name, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
+ mBiometricStateCallback, mAuthenticationStateListeners, props, name,
+ mLockoutResetDispatcher, BiometricContext.getInstance(getContext()),
false /* resetLockoutRequiresChallenge */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -881,7 +905,7 @@
if (Flags.deHidl()) {
mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
- getContext(), mBiometricStateCallback,
+ getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
filteredSensorProps.second,
filteredSensorProps.first, mLockoutResetDispatcher,
BiometricContext.getInstance(getContext()),
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 22e399c..f35de93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.NotificationManager;
@@ -44,6 +46,7 @@
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -77,6 +80,8 @@
private ICancellationSignal mCancellationSignal;
@Nullable
private final SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
@FaceManager.FaceAcquired
private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
@@ -89,11 +94,13 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength) {
+ @Authenticators.Types int sensorStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
this(context, lazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
- context.getSystemService(SensorPrivacyManager.class), sensorStrength);
+ context.getSystemService(SensorPrivacyManager.class), sensorStrength,
+ authenticationStateListeners);
}
@VisibleForTesting
@@ -107,7 +114,8 @@
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
SensorPrivacyManager sensorPrivacyManager,
- @Authenticators.Types int biometricStrength) {
+ @Authenticators.Types int biometricStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */, lockoutTracker,
@@ -118,6 +126,7 @@
mNotificationManager = context.getSystemService(NotificationManager.class);
mSensorPrivacyManager = sensorPrivacyManager;
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+ mAuthenticationStateListeners = authenticationStateListeners;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -262,6 +271,16 @@
0 /* error */,
0 /* vendorError */,
getTargetUserId()));
+
+ if (reportBiometricAuthAttempts()) {
+ if (authenticated) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ } else {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index e4ecf1a..d01c268 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -59,6 +59,7 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -103,6 +104,8 @@
@NonNull
private final BiometricStateCallback mBiometricStateCallback;
@NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
+ @NonNull
private final String mHalInstanceName;
@NonNull
private final Handler mHandler;
@@ -156,18 +159,20 @@
public FaceProvider(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
boolean resetLockoutRequiresChallenge) {
- this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
- biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge,
- false /* testHalEnabled */);
+ this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+ lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
+ resetLockoutRequiresChallenge, false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -178,6 +183,7 @@
boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
+ mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
@@ -610,7 +616,8 @@
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mUsageStats, lockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId),
+ mAuthenticationStateListeners);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientStarted(
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 5337666..48a676c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -64,6 +64,7 @@
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -119,6 +120,8 @@
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
@NonNull private final BiometricStateCallback mBiometricStateCallback;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
@NonNull private final Handler mHandler;
@@ -350,6 +353,7 @@
@VisibleForTesting
Face10(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
@@ -358,6 +362,7 @@
mSensorProperties = sensorProps;
mContext = context;
mBiometricStateCallback = biometricStateCallback;
+ mAuthenticationStateListeners = authenticationStateListeners;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
@@ -392,11 +397,12 @@
public static Face10 newInstance(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
- handler, new BiometricScheduler<>(
+ return new Face10(context, biometricStateCallback, authenticationStateListeners,
+ sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */),
BiometricContext.getInstance(context));
@@ -846,7 +852,8 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector), mBiometricContext,
isStrongBiometric, mUsageStats, mLockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+ mAuthenticationStateListeners);
mScheduler.scheduleClientMonitor(client);
}
@@ -860,7 +867,8 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector), mBiometricContext,
isStrongBiometric, mLockoutTracker, mUsageStats,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+ mAuthenticationStateListeners);
mScheduler.scheduleClientMonitor(client);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 8ab8892..e44b263 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.hidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
@@ -36,6 +38,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -65,6 +68,8 @@
private int mLastAcquire;
private SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon,
@@ -75,7 +80,8 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
@NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength) {
+ @Authenticators.Types int sensorStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */,
@@ -84,6 +90,7 @@
setRequestId(requestId);
mUsageStats = usageStats;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mAuthenticationStateListeners = authenticationStateListeners;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -186,6 +193,16 @@
0 /* error */,
0 /* vendorError */,
getTargetUserId()));
+
+ if (reportBiometricAuthAttempts()) {
+ if (authenticated) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ } else {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index f7e8123..6912961 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
import android.annotation.NonNull;
@@ -232,8 +234,16 @@
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ }
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 4c1d4d6..7a329e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.hidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
import android.annotation.NonNull;
@@ -142,6 +144,10 @@
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ }
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
final @LockoutTracker.LockoutMode int lockoutMode =
@@ -161,6 +167,10 @@
onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
cancel();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
}
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9dd7daf..9102cfd 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -286,6 +286,9 @@
return new CompatChange(changeId);
});
c.addPackageOverride(packageName, overrides, allowedState, versionCode);
+ Slog.d(TAG, (overrides.isEnabled() ? "Enabled" : "Disabled")
+ + " change " + changeId + (c.getName() != null ? " [" + c.getName() + "]" : "")
+ + " for " + packageName);
invalidateCache();
return alreadyKnown.get();
}
@@ -372,7 +375,14 @@
long changeId = change.getId();
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
- return change.removePackageOverride(packageName, allowedState, versionCode);
+ boolean overrideExists = change.removePackageOverride(packageName, allowedState,
+ versionCode);
+ if (overrideExists) {
+ Slog.d(TAG, "Reset change " + changeId
+ + (change.getName() != null ? " [" + change.getName() + "]" : "")
+ + " for " + packageName + " to default value.");
+ }
+ return overrideExists;
}
/**
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 6ec6a12..77cb08b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -204,6 +204,10 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {}
+
+ @Override
public void onProcessDied(int pid, int uid) {}
@Override
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index bc55b24..572d844 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -16,6 +16,8 @@
package com.android.server.input;
+import static com.android.input.flags.Flags.rateLimitUserActivityPokeInDispatcher;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -113,6 +115,8 @@
for (Consumer<String> observer : mObservers.values()) {
observer.accept("just booted");
}
+
+ configureUserActivityPokeInterval();
}
@Override
@@ -228,4 +232,13 @@
mService.setAccessibilityStickyKeysEnabled(
InputSettings.isAccessibilityStickyKeysEnabled(mContext));
}
+
+ private void configureUserActivityPokeInterval() {
+ if (rateLimitUserActivityPokeInDispatcher()) {
+ final int intervalMillis = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_minMillisBetweenInputUserActivityEvents);
+ Log.i(TAG, "Setting user activity interval (ms) of " + intervalMillis);
+ mNative.setMinTimeBetweenUserActivityPokes(intervalMillis);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 6f52020..8aec8ca 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -108,6 +108,8 @@
void setFocusedDisplay(int displayId);
+ void setMinTimeBetweenUserActivityPokes(long millis);
+
boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
@@ -344,6 +346,9 @@
public native void setFocusedDisplay(int displayId);
@Override
+ public native void setMinTimeBetweenUserActivityPokes(long millis);
+
+ @Override
public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 403b421..71a9f54 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -248,6 +248,7 @@
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
if (subManager != null && telManager != null) {
+ subManager = subManager.createForAllUserProfiles();
List<SubscriptionInfo> subscriptionInfoList =
subManager.getActiveSubscriptionInfoList();
HashSet<Integer> activeSubIds = new HashSet<Integer>();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 542b3b0..a06607b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -16,6 +16,7 @@
package com.android.server.locksettings;
+import static android.security.Flags.reportPrimaryAuthAttempts;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
@@ -92,6 +93,7 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -137,6 +139,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
@@ -329,6 +332,9 @@
private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
+ private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners =
+ new RemoteCallbackList<>();
+
// This class manages life cycle events for encrypted users on File Based Encryption (FBE)
// devices. The most basic of these is to show/hide notifications about missing features until
// the user unlocks the account and credential-encrypted storage is available.
@@ -2364,9 +2370,37 @@
requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
}
}
+ if (reportPrimaryAuthAttempts()) {
+ final boolean success =
+ response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
+ notifyLockSettingsStateListeners(success, userId);
+ }
return response;
}
+ private void notifyLockSettingsStateListeners(boolean success, int userId) {
+ int i = mLockSettingsStateListeners.beginBroadcast();
+ try {
+ while (i > 0) {
+ i--;
+ try {
+ if (success) {
+ mLockSettingsStateListeners.getBroadcastItem(i)
+ .onAuthenticationSucceeded(userId);
+ } else {
+ mLockSettingsStateListeners.getBroadcastItem(i)
+ .onAuthenticationFailed(userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying LockSettingsStateListener:"
+ + " success = " + success + ", userId = " + userId, e);
+ }
+ }
+ } finally {
+ mLockSettingsStateListeners.finishBroadcast();
+ }
+ }
+
@Override
public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
int userId, @LockPatternUtils.VerifyFlag int flags) {
@@ -3684,6 +3718,18 @@
public void refreshStrongAuthTimeout(int userId) {
mStrongAuth.refreshStrongAuthTimeout(userId);
}
+
+ @Override
+ public void registerLockSettingsStateListener(
+ @NonNull ILockSettingsStateListener listener) {
+ mLockSettingsStateListeners.register(listener);
+ }
+
+ @Override
+ public void unregisterLockSettingsStateListener(
+ @NonNull ILockSettingsStateListener listener) {
+ mLockSettingsStateListeners.unregister(listener);
+ }
}
private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 550aed5..978f468 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -214,6 +214,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid,
+ String packageName, String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
serviceTypes);
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
new file mode 100644
index 0000000..71d8e6b
--- /dev/null
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "net_flags",
+ package: "com.android.server.net",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "net_flags_lib",
+ aconfig_declarations: "net_flags",
+}
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 681d1a0..d25f529 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -17,6 +17,7 @@
package com.android.server.net;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -27,6 +28,7 @@
import static android.net.INetd.FIREWALL_DENYLIST;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -187,6 +189,13 @@
*/
@GuardedBy("mRulesLock")
private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+
+ /**
+ * Contains the per-UID firewall rules that are used when Background chain is enabled.
+ */
+ @GuardedBy("mRulesLock")
+ private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -449,13 +458,15 @@
syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
final int[] chains = {
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_BACKGROUND,
};
for (int chain : chains) {
@@ -1206,6 +1217,8 @@
return FIREWALL_CHAIN_NAME_RESTRICTED;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return FIREWALL_CHAIN_NAME_BACKGROUND;
default:
throw new IllegalArgumentException("Bad child chain: " + chain);
}
@@ -1223,6 +1236,8 @@
return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return FIREWALL_ALLOWLIST;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return FIREWALL_ALLOWLIST;
default:
return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
}
@@ -1343,6 +1358,8 @@
return mUidFirewallRestrictedRules;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return mUidFirewallLowPowerStandbyRules;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return mUidFirewallBackgroundRules;
case FIREWALL_CHAIN_NONE:
return mUidFirewallRules;
default:
@@ -1395,6 +1412,10 @@
pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
mUidFirewallLowPowerStandbyRules);
+
+ pw.print("UID firewall background chain enabled: ");
+ pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
+ dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
}
pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1494,6 +1515,11 @@
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
return true;
}
+ if (getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)
+ && mUidFirewallBackgroundRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
+ return true;
+ }
if (mUidRejectOnMetered.get(uid)) {
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
+ " in the background");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index d7188c7..8e2d778 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,6 +16,7 @@
package com.android.server.net;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -24,6 +25,7 @@
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -389,6 +391,8 @@
return FIREWALL_CHAIN_NAME_RESTRICTED;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return FIREWALL_CHAIN_NAME_BACKGROUND;
default:
return String.valueOf(chain);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e4e48bd..f9ffb1c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -47,6 +47,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -54,6 +55,7 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -77,6 +79,7 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
@@ -96,6 +99,7 @@
import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
import static android.net.NetworkPolicyManager.allowedReasonsToString;
import static android.net.NetworkPolicyManager.blockedReasonsToString;
+import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -202,12 +206,12 @@
import android.os.MessageQueue.IdleHandler;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
-import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -243,6 +247,7 @@
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.SparseSetArray;
+import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.R;
@@ -458,6 +463,12 @@
*/
private static final int MSG_UIDS_BLOCKED_REASONS_CHANGED = 23;
+ /**
+ * Message to update background restriction rules for uids that should lose network access
+ * due to being in the background.
+ */
+ private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24;
+
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -476,7 +487,7 @@
private ConnectivityManager mConnManager;
private PowerManagerInternal mPowerManagerInternal;
- private PowerWhitelistManager mPowerWhitelistManager;
+ private PowerExemptionManager mPowerExemptionManager;
@NonNull
private final Dependencies mDeps;
@@ -491,6 +502,12 @@
// Denotes the status of restrict background read from disk.
private boolean mLoadedRestrictBackground;
+ /**
+ * Whether or not network for apps in proc-states greater than
+ * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
+ */
+ private boolean mBackgroundNetworkRestricted;
+
// See main javadoc for instructions on how to use these locks.
final Object mUidRulesFirstLock = new Object();
final Object mNetworkPoliciesSecondLock = new Object();
@@ -515,6 +532,15 @@
private volatile boolean mNetworkManagerReady;
+ /**
+ * Delay after which a uid going into a process state greater than or equal to
+ * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} will lose network access.
+ * The delay is meant to prevent churn due to quick process-state changes.
+ * Note that there is no delay while granting network access.
+ */
+ @VisibleForTesting
+ long mBackgroundRestrictionDelayMs = TimeUnit.SECONDS.toMillis(5);
+
/** Defined network policies. */
@GuardedBy("mNetworkPoliciesSecondLock")
final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
@@ -546,6 +572,8 @@
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
@GuardedBy("mUidRulesFirstLock")
+ final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+ @GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
@@ -625,6 +653,14 @@
@GuardedBy("mUidRulesFirstLock")
private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
+ /**
+ * Stores a map of uids to the time their transition to background is considered complete. They
+ * will lose network access after this time. This is used to prevent churn in rules due to quick
+ * process-state transitions.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private final SparseLongArray mBackgroundTransitioningUids = new SparseLongArray();
+
/** Map from network ID to last observed meteredness state */
@GuardedBy("mNetworkPoliciesSecondLock")
private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -824,7 +860,7 @@
mContext = Objects.requireNonNull(context, "missing context");
mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
- mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+ mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
mClock = Objects.requireNonNull(clock, "missing Clock");
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
@@ -860,15 +896,15 @@
@GuardedBy("mUidRulesFirstLock")
private void updatePowerSaveAllowlistUL() {
- int[] whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ false);
+ int[] allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ false);
mPowerSaveWhitelistExceptIdleAppIds.clear();
- for (int uid : whitelist) {
+ for (int uid : allowlist) {
mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
}
- whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ true);
+ allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ true);
mPowerSaveWhitelistAppIds.clear();
- for (int uid : whitelist) {
+ for (int uid : allowlist) {
mPowerSaveWhitelistAppIds.put(uid, true);
}
}
@@ -1018,6 +1054,14 @@
writePolicyAL();
}
+ // The flag is boot-stable.
+ mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
+ if (mBackgroundNetworkRestricted) {
+ // Firewall rules and UidBlockedState will get updated in
+ // updateRulesForGlobalChangeAL below.
+ enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
+ }
+
setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
updateRulesForGlobalChangeAL(false);
updateNotificationsNL();
@@ -1028,17 +1072,22 @@
final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_CAPABILITY;
+
+ final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
+ : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
+ // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint.
+
mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
- NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
+ cutpoint, "android");
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
// listen for changes to power save allowlist
- final IntentFilter whitelistFilter = new IntentFilter(
+ final IntentFilter allowlistFilter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
+ mContext.registerReceiver(mPowerSaveAllowlistReceiver, allowlistFilter, null, mHandler);
// watch for network interfaces to be claimed
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
@@ -1189,12 +1238,15 @@
}
}
- final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mPowerSaveAllowlistReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
synchronized (mUidRulesFirstLock) {
updatePowerSaveAllowlistUL();
+ if (mBackgroundNetworkRestricted) {
+ updateRulesForBackgroundChainUL();
+ }
updateRulesForRestrictPowerUL();
updateRulesForAppIdleUL();
}
@@ -3914,6 +3966,11 @@
}
fout.println();
+ fout.println("Flags:");
+ fout.println("Network blocked for TOP_SLEEPING and above: "
+ + mBackgroundNetworkRestricted);
+
+ fout.println();
fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
fout.println("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
fout.println("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
@@ -4055,6 +4112,22 @@
fout.decreaseIndent();
}
+ size = mBackgroundTransitioningUids.size();
+ if (size > 0) {
+ final long nowUptime = SystemClock.uptimeMillis();
+ fout.println("Uids transitioning to background:");
+ fout.increaseIndent();
+ for (int i = 0; i < size; i++) {
+ fout.print("UID=");
+ fout.print(mBackgroundTransitioningUids.keyAt(i));
+ fout.print(", ");
+ TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), nowUptime,
+ fout);
+ fout.println();
+ }
+ fout.decreaseIndent();
+ }
+
final SparseBooleanArray knownUids = new SparseBooleanArray();
collectKeys(mUidState, knownUids);
synchronized (mUidBlockedState) {
@@ -4182,6 +4255,12 @@
return isProcStateAllowedWhileInLowPowerStandby(uidState);
}
+ @GuardedBy("mUidRulesFirstLock")
+ private boolean isUidExemptFromBackgroundRestrictions(int uid) {
+ return mBackgroundTransitioningUids.indexOfKey(uid) >= 0
+ || isProcStateAllowedNetworkWhileBackground(mUidState.get(uid));
+ }
+
/**
* Process state of UID changed; if needed, will trigger
* {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -4207,6 +4286,8 @@
// state changed, push updated rules
mUidState.put(uid, newUidState);
updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
+
+ boolean updatePowerRestrictionRules = false;
boolean allowedWhileIdleOrPowerSaveModeChanged =
isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
!= isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
@@ -4218,19 +4299,44 @@
if (mRestrictPower) {
updateRuleForRestrictPowerUL(uid);
}
- updateRulesForPowerRestrictionsUL(uid, procState);
+ updatePowerRestrictionRules = true;
+ }
+ if (mBackgroundNetworkRestricted) {
+ final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+ oldUidState);
+ final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+ if (!wasAllowed && isAllowed) {
+ mBackgroundTransitioningUids.delete(uid);
+ updateRuleForBackgroundUL(uid);
+ updatePowerRestrictionRules = true;
+ } else if (wasAllowed && !isAllowed) {
+ final long completionTimeMs = SystemClock.uptimeMillis()
+ + mBackgroundRestrictionDelayMs;
+ if (mBackgroundTransitioningUids.indexOfKey(uid) < 0) {
+ // This is just a defensive check in case the upstream code ever makes
+ // multiple calls for the same process state change.
+ mBackgroundTransitioningUids.put(uid, completionTimeMs);
+ }
+ if (!mHandler.hasMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS)) {
+ // Many uids may be in this "transitioning" state at the same time, so
+ // using one message at a time to avoid congestion in the MessageQueue.
+ mHandler.sendEmptyMessageAtTime(
+ MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+ }
+ }
}
if (mLowPowerStandbyActive) {
boolean allowedInLpsChanged =
isProcStateAllowedWhileInLowPowerStandby(oldUidState)
!= isProcStateAllowedWhileInLowPowerStandby(newUidState);
if (allowedInLpsChanged) {
- if (!allowedWhileIdleOrPowerSaveModeChanged) {
- updateRulesForPowerRestrictionsUL(uid, procState);
- }
updateRuleForLowPowerStandbyUL(uid);
+ updatePowerRestrictionRules = true;
}
}
+ if (updatePowerRestrictionRules) {
+ updateRulesForPowerRestrictionsUL(uid, procState);
+ }
return true;
}
} finally {
@@ -4253,6 +4359,12 @@
if (mRestrictPower) {
updateRuleForRestrictPowerUL(uid);
}
+ if (mBackgroundNetworkRestricted) {
+ // Uid is no longer running, there is no point in any grace period of network
+ // access during transitions to lower importance proc-states.
+ mBackgroundTransitioningUids.delete(uid);
+ updateRuleForBackgroundUL(uid);
+ }
updateRulesForPowerRestrictionsUL(uid);
if (mLowPowerStandbyActive) {
updateRuleForLowPowerStandbyUL(uid);
@@ -4460,11 +4572,41 @@
}
}
+ /**
+ * Updates the rules for apps allowlisted to use network while in the background.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private void updateRulesForBackgroundChainUL() {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
+ try {
+ final SparseIntArray uidRules = mUidFirewallBackgroundRules;
+ uidRules.clear();
+
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int ui = users.size() - 1; ui >= 0; ui--) {
+ final UserInfo user = users.get(ui);
+ updateRulesForAllowlistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
+ updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
+ updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistExceptIdleAppIds,
+ user.id);
+ }
+ for (int i = mUidState.size() - 1; i >= 0; i--) {
+ if (mBackgroundTransitioningUids.indexOfKey(mUidState.keyAt(i)) >= 0
+ || isProcStateAllowedNetworkWhileBackground(mUidState.valueAt(i))) {
+ uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
+ }
+ }
+ setUidFirewallRulesUL(FIREWALL_CHAIN_BACKGROUND, uidRules);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ }
+ }
+
private void updateRulesForAllowlistedAppIds(final SparseIntArray uidRules,
- final SparseBooleanArray whitelistedAppIds, int userId) {
- for (int i = whitelistedAppIds.size() - 1; i >= 0; --i) {
- if (whitelistedAppIds.valueAt(i)) {
- final int appId = whitelistedAppIds.keyAt(i);
+ final SparseBooleanArray allowlistedAppIds, int userId) {
+ for (int i = allowlistedAppIds.size() - 1; i >= 0; --i) {
+ if (allowlistedAppIds.valueAt(i)) {
+ final int appId = allowlistedAppIds.keyAt(i);
final int uid = UserHandle.getUid(userId, appId);
uidRules.put(uid, FIREWALL_RULE_ALLOW);
}
@@ -4523,12 +4665,12 @@
@GuardedBy("mUidRulesFirstLock")
private boolean isAllowlistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
final int appId = UserHandle.getAppId(uid);
- boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
+ boolean allowlisted = mPowerSaveTempWhitelistAppIds.get(appId)
|| mPowerSaveWhitelistAppIds.get(appId);
if (!deviceIdleMode) {
- isWhitelisted = isWhitelisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
+ allowlisted = allowlisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
}
- return isWhitelisted;
+ return allowlisted;
}
/**
@@ -4617,6 +4759,38 @@
}
/**
+ * Update firewall rule for a single uid whenever there are any interesting changes in the uid.
+ * Currently, it is called when:
+ * - The uid is added to or removed from power allowlists
+ * - The uid undergoes a process-state change
+ * - A package belonging to this uid is added
+ * - The uid is evicted from memory
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ void updateRuleForBackgroundUL(int uid) {
+ if (!isUidValidForAllowlistRulesUL(uid)) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForBackgroundUL: " + uid);
+ try {
+ // The uid should be absent from mUidState and mBackgroundTransitioningUids if it is
+ // not running when this method is called. Then, the firewall state will depend on the
+ // allowlist alone. This is the desired behavior.
+ if (isAllowlistedFromPowerSaveUL(uid, false)
+ || isUidExemptFromBackgroundRestrictions(uid)) {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW);
+ if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL ALLOW " + uid);
+ } else {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT);
+ if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL " + uid + " to DEFAULT");
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ /**
* Toggle the firewall standby chain and inform listeners if the uid rules have effectively
* changed.
*/
@@ -4663,6 +4837,9 @@
"updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
}
try {
+ if (mBackgroundNetworkRestricted) {
+ updateRulesForBackgroundChainUL();
+ }
updateRulesForAppIdleUL();
updateRulesForRestrictPowerUL();
updateRulesForRestrictBackgroundUL();
@@ -4822,6 +4999,9 @@
updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
updateRuleForDeviceIdleUL(uid);
updateRuleForRestrictPowerUL(uid);
+ if (mBackgroundNetworkRestricted) {
+ updateRuleForBackgroundUL(uid);
+ }
// Update internal rules.
updateRulesForPowerRestrictionsUL(uid);
}
@@ -4959,6 +5139,8 @@
mUidFirewallStandbyRules.delete(uid);
mUidFirewallDozableRules.delete(uid);
mUidFirewallPowerSaveRules.delete(uid);
+ mUidFirewallBackgroundRules.delete(uid);
+ mBackgroundTransitioningUids.delete(uid);
mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
mPowerSaveWhitelistAppIds.delete(uid);
mPowerSaveTempWhitelistAppIds.delete(uid);
@@ -4992,6 +5174,9 @@
updateRuleForDeviceIdleUL(uid);
updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
updateRuleForRestrictPowerUL(uid);
+ if (mBackgroundNetworkRestricted) {
+ updateRuleForBackgroundUL(uid);
+ }
// If the uid has the necessary permissions, then it should be added to the restricted mode
// firewall allowlist.
@@ -5176,7 +5361,6 @@
* Similar to above but ignores idle state if app standby is currently disabled by parole.
*
* @param uid the uid of the app to update rules for
- * @param oldUidRules the current rules for the uid, in order to determine if there's a change
* @param isUidIdle whether uid is idle or not
*/
@GuardedBy("mUidRulesFirstLock")
@@ -5222,6 +5406,7 @@
newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
+ newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5234,6 +5419,9 @@
& ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
+ newAllowedReasons |= (mBackgroundNetworkRestricted
+ && isUidExemptFromBackgroundRestrictions(uid))
+ ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
& BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -5255,7 +5443,7 @@
oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
- uidRules = oldEffectiveBlockedReasons == newEffectiveBlockedReasons
+ uidRules = (oldEffectiveBlockedReasons == newEffectiveBlockedReasons)
? RULE_NONE
: uidBlockedState.deriveUidRules();
}
@@ -5448,6 +5636,28 @@
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
return true;
}
+ case MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS: {
+ final long now = SystemClock.uptimeMillis();
+ long nextCheckTime = Long.MAX_VALUE;
+ synchronized (mUidRulesFirstLock) {
+ for (int i = mBackgroundTransitioningUids.size() - 1; i >= 0; i--) {
+ final long completionTimeMs = mBackgroundTransitioningUids.valueAt(i);
+ if (completionTimeMs > now) {
+ nextCheckTime = Math.min(nextCheckTime, completionTimeMs);
+ continue;
+ }
+ final int uid = mBackgroundTransitioningUids.keyAt(i);
+ mBackgroundTransitioningUids.removeAt(i);
+ updateRuleForBackgroundUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, false);
+ }
+ }
+ if (nextCheckTime < Long.MAX_VALUE) {
+ mHandler.sendEmptyMessageAtTime(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS,
+ nextCheckTime);
+ }
+ return true;
+ }
case MSG_POLICIES_CHANGED: {
final int uid = msg.arg1;
final int policy = msg.arg2;
@@ -5859,6 +6069,8 @@
mUidFirewallRestrictedModeRules.put(uid, rule);
} else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
+ mUidFirewallBackgroundRules.put(uid, rule);
}
try {
@@ -5915,6 +6127,8 @@
FIREWALL_RULE_DEFAULT);
mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
FIREWALL_RULE_DEFAULT);
+ mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
+ FIREWALL_RULE_DEFAULT);
mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
mLogger.meteredAllowlistChanged(uid, false);
mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
@@ -6441,10 +6655,12 @@
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
}
if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
}
if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
@@ -6455,19 +6671,24 @@
if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
}
+ if ((allowedReasons & ALLOWED_REASON_NOT_IN_BACKGROUND) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
+ }
return effectiveBlockedReasons;
}
static int getAllowedReasonsForProcState(int procState) {
- if (procState > NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
- return ALLOWED_REASON_NONE;
- } else if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
+ if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
return ALLOWED_REASON_TOP | ALLOWED_REASON_FOREGROUND
- | ALLOWED_METERED_REASON_FOREGROUND;
- } else {
- return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND;
+ | ALLOWED_METERED_REASON_FOREGROUND | ALLOWED_REASON_NOT_IN_BACKGROUND;
+ } else if (procState <= NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
+ return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND
+ | ALLOWED_REASON_NOT_IN_BACKGROUND;
+ } else if (procState < NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE) {
+ return ALLOWED_REASON_NOT_IN_BACKGROUND;
}
+ return ALLOWED_REASON_NONE;
}
@Override
@@ -6492,6 +6713,7 @@
BLOCKED_REASON_APP_STANDBY,
BLOCKED_REASON_RESTRICTED_MODE,
BLOCKED_REASON_LOW_POWER_STANDBY,
+ BLOCKED_REASON_APP_BACKGROUND,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -6505,6 +6727,7 @@
ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
+ ALLOWED_REASON_NOT_IN_BACKGROUND,
ALLOWED_METERED_REASON_USER_EXEMPTED,
ALLOWED_METERED_REASON_SYSTEM,
ALLOWED_METERED_REASON_FOREGROUND,
@@ -6524,6 +6747,8 @@
return "RESTRICTED_MODE";
case BLOCKED_REASON_LOW_POWER_STANDBY:
return "LOW_POWER_STANDBY";
+ case BLOCKED_REASON_APP_BACKGROUND:
+ return "APP_BACKGROUND";
case BLOCKED_METERED_REASON_DATA_SAVER:
return "DATA_SAVER";
case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -6554,6 +6779,8 @@
return "RESTRICTED_MODE_PERMISSIONS";
case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
return "LOW_POWER_STANDBY_ALLOWLIST";
+ case ALLOWED_REASON_NOT_IN_BACKGROUND:
+ return "NOT_IN_BACKGROUND";
case ALLOWED_METERED_REASON_USER_EXEMPTED:
return "METERED_USER_EXEMPTED";
case ALLOWED_METERED_REASON_SYSTEM:
@@ -6621,7 +6848,8 @@
int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
| BLOCKED_REASON_DOZE
| BLOCKED_REASON_BATTERY_SAVER
- | BLOCKED_REASON_LOW_POWER_STANDBY;
+ | BLOCKED_REASON_LOW_POWER_STANDBY
+ | BLOCKED_REASON_APP_BACKGROUND;
if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
uidRule |= RULE_REJECT_ALL;
} else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
new file mode 100644
index 0000000..419665a
--- /dev/null
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.net"
+
+flag {
+ name: "network_blocked_for_top_sleeping_and_above"
+ namespace: "backstage_power"
+ description: "Block network access for apps in a low importance background state"
+ bug: "304347838"
+}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 85c4ffe..f852b81 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -57,6 +57,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -71,7 +72,6 @@
import com.android.server.EventLogTags;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
-import com.android.server.notification.Flags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -81,6 +81,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* NotificationManagerService helper for handling notification attention effects:
@@ -100,6 +101,20 @@
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
+ @VisibleForTesting
+ static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED,
+ Intent.ACTION_BOOT_COMPLETED,
+ Intent.ACTION_USER_SWITCHED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE
+ );
+
+ @VisibleForTesting
+ static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false),
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
+ );
+
private final Context mContext;
private final PackageManager mPackageManager;
private final TelephonyManager mTelephonyManager;
@@ -191,7 +206,7 @@
mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
if (Flags.politeNotifications()) {
- mStrategy = getPolitenessStrategy();
+ mStrategy = createPolitenessStrategy();
} else {
mStrategy = null;
}
@@ -200,7 +215,7 @@
loadUserSettings();
}
- private PolitenessStrategy getPolitenessStrategy() {
+ private PolitenessStrategy createPolitenessStrategy() {
if (Flags.crossAppPoliteNotifications()) {
PolitenessStrategy appStrategy = new StrategyPerApp(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -209,11 +224,12 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
- return new StrategyGlobal(
+ return new StrategyAvalanche(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
appStrategy);
} else {
return new StrategyPerApp(
@@ -225,6 +241,11 @@
}
}
+ @VisibleForTesting
+ PolitenessStrategy getPolitenessStrategy() {
+ return mStrategy;
+ }
+
public void onSystemReady() {
mSystemReady = true;
@@ -259,6 +280,11 @@
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ if (Flags.crossAppPoliteNotifications()) {
+ for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ filter.addAction(avalancheIntent);
+ }
+ }
mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
mContext.getContentResolver().registerContentObserver(
@@ -1052,7 +1078,8 @@
}
}
- abstract private static class PolitenessStrategy {
+ @VisibleForTesting
+ abstract static class PolitenessStrategy {
static final int POLITE_STATE_DEFAULT = 0;
static final int POLITE_STATE_POLITE = 1;
static final int POLITE_STATE_MUTED = 2;
@@ -1079,6 +1106,8 @@
protected boolean mApplyPerPackage;
protected final Map<String, Long> mLastUpdatedTimestampByPackage;
+ protected boolean mIsActive = true;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
int volumeMuted) {
mVolumeStates = new HashMap<>();
@@ -1218,6 +1247,10 @@
}
return nextState;
}
+
+ boolean isActive() {
+ return mIsActive;
+ }
}
// TODO b/270456865: Only one of the two strategies will be released.
@@ -1289,55 +1322,60 @@
}
/**
- * Global (cross-app) strategy.
+ * Avalanche (cross-app) strategy.
*/
- private static class StrategyGlobal extends PolitenessStrategy {
+ private static class StrategyAvalanche extends PolitenessStrategy {
private static final String COMMON_KEY = "cross_app_common_key";
private final PolitenessStrategy mAppStrategy;
private long mLastNotificationTimestamp = 0;
- public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, PolitenessStrategy appStrategy) {
+ private final int mTimeoutAvalanche;
+ private long mLastAvalancheTriggerTimestamp = 0;
+
+ StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ mTimeoutAvalanche = timeoutAvalanche;
mAppStrategy = appStrategy;
if (DEBUG) {
- Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " "
+ + timeoutAvalanche);
}
}
@Override
void onNotificationPosted(NotificationRecord record) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
+ if (isAvalancheActive()) {
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
- long timeSinceLastNotif =
+ long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
- final String key = getChannelKey(record);
- @PolitenessState final int currState = getPolitenessState(record);
- @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ final String key = getChannelKey(record);
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
- if (DEBUG) {
- Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
- + " vol state: " + nextState + " key: " + key);
+ if (DEBUG) {
+ Log.i(TAG,
+ "StrategyAvalanche onNotificationPosted time delta: "
+ + timeSinceLastNotif
+ + " vol state: " + nextState + " key: " + key);
+ }
+
+ mVolumeStates.put(key, nextState);
}
- mVolumeStates.put(key, nextState);
-
mAppStrategy.onNotificationPosted(record);
}
@Override
public float getSoundVolume(final NotificationRecord record) {
- final @PolitenessState int globalVolState = getPolitenessState(record);
- final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
-
- // Prioritize the most polite outcome
- if (globalVolState > appVolState) {
+ if (isAvalancheActive()) {
return super.getSoundVolume(record);
} else {
return mAppStrategy.getSoundVolume(record);
@@ -1382,6 +1420,24 @@
super.setApplyCooldownPerPackage(applyPerPackage);
mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
}
+
+ boolean isAvalancheActive() {
+ mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp
+ < mTimeoutAvalanche);
+ if (DEBUG) {
+ Log.i(TAG, "StrategyAvalanche: active " + mIsActive);
+ }
+ return mIsActive;
+ }
+
+ @Override
+ boolean isActive() {
+ return isAvalancheActive();
+ }
+
+ void setTriggerTimeMs(long timestamp) {
+ mLastAvalancheTriggerTimestamp = timestamp;
+ }
}
//====================== Observers =============================
@@ -1415,6 +1471,30 @@
|| action.equals(Intent.ACTION_USER_UNLOCKED)) {
loadUserSettings();
}
+
+ if (Flags.crossAppPoliteNotifications()) {
+ if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) {
+ boolean enableAvalancheStrategy = true;
+ // Some actions must also match extras, ie. airplane mode => disabled
+ Pair<String, Boolean> expectedExtras =
+ NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action);
+ if (expectedExtras != null) {
+ enableAvalancheStrategy =
+ intent.getBooleanExtra(expectedExtras.first, false)
+ == expectedExtras.second;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "Avalanche trigger intent received: " + action
+ + ". Enabling avalanche strategy: " + enableAvalancheStrategy);
+ }
+
+ if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) {
+ ((StrategyAvalanche) mStrategy)
+ .setTriggerTimeMs(System.currentTimeMillis());
+ }
+ }
+ }
}
};
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f311034..ada79ae 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4217,8 +4217,10 @@
}
}
+ final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
+ ? System.currentTimeMillis() : 0;
final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
- scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
+ scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index ee5875e..68f6ca1 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -88,6 +88,13 @@
final boolean didRestore = (msg.arg2 != 0);
mPm.mRunningInstalls.delete(msg.arg1);
+ if (request == null) {
+ if (DEBUG_INSTALL) {
+ Slog.i(TAG, "InstallRequest is null. Nothing to do for post-install "
+ + "token " + msg.arg1);
+ }
+ break;
+ }
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
@@ -116,10 +123,19 @@
}
} break;
case WRITE_SETTINGS: {
- mPm.writeSettings(/*sync=*/false);
+ if (!mPm.tryWriteSettings(/*sync=*/false)) {
+ // Failed to write.
+ this.removeMessages(WRITE_SETTINGS);
+ mPm.scheduleWriteSettings();
+ }
} break;
case WRITE_PACKAGE_LIST: {
- mPm.writePackageList(msg.arg1);
+ int userId = msg.arg1;
+ if (!mPm.tryWritePackageList(userId)) {
+ // Failed to write.
+ this.removeMessages(WRITE_PACKAGE_LIST);
+ mPm.scheduleWritePackageList(userId);
+ }
} break;
case CHECK_PENDING_VERIFICATION: {
final int verificationId = msg.arg1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 46f5523..135bd4f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -489,6 +489,9 @@
*/
static final long WATCHDOG_TIMEOUT = 1000*60*10; // ten minutes
+ // How long to wait for Lock in async writeSettings and writePackageList.
+ private static final long WRITE_LOCK_TIMEOUT_MS = 1000 * 10; // 10 seconds
+
/**
* Default IncFs timeouts. Maximum values in IncFs is 1hr.
*
@@ -1564,7 +1567,7 @@
}
}
- private void scheduleWritePackageListLocked(int userId) {
+ void scheduleWritePackageList(int userId) {
invalidatePackageInfoCache();
if (!mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
Message msg = mHandler.obtainMessage(WRITE_PACKAGE_LIST);
@@ -1616,22 +1619,41 @@
mSettings.writePackageRestrictions(dirtyUsers);
}
- void writeSettings(boolean sync) {
- synchronized (mLock) {
+ private boolean tryUnderLock(boolean sync, long timeoutMs, Runnable runnable) {
+ try {
+ if (sync) {
+ mLock.lock();
+ } else if (!mLock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
+ return false;
+ }
+ try {
+ runnable.run();
+ return true;
+ } finally {
+ mLock.unlock();
+ }
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Failed to obtain mLock", e);
+ }
+ return false;
+ }
+
+ boolean tryWriteSettings(boolean sync) {
+ return tryUnderLock(sync, WRITE_LOCK_TIMEOUT_MS, () -> {
mHandler.removeMessages(WRITE_SETTINGS);
mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
writeSettingsLPrTEMP(sync);
synchronized (mDirtyUsers) {
mDirtyUsers.clear();
}
- }
+ });
}
- void writePackageList(int userId) {
- synchronized (mLock) {
+ boolean tryWritePackageList(int userId) {
+ return tryUnderLock(/*sync=*/false, WRITE_LOCK_TIMEOUT_MS, () -> {
mHandler.removeMessages(WRITE_PACKAGE_LIST);
mSettings.writePackageListLPr(userId);
- }
+ });
}
private static final Handler.Callback BACKGROUND_HANDLER_CALLBACK = new Handler.Callback() {
@@ -3056,7 +3078,9 @@
if (mHandler.hasMessages(WRITE_SETTINGS)
|| mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)
|| mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
- writeSettings(/*sync=*/true);
+ while (!tryWriteSettings(/*sync=*/true)) {
+ Slog.wtf(TAG, "Failed to write settings on shutdown");
+ }
}
}
}
@@ -4363,7 +4387,7 @@
}
synchronized (mLock) {
scheduleWritePackageRestrictions(userId);
- scheduleWritePackageListLocked(userId);
+ scheduleWritePackageList(userId);
mAppsFilter.onUserCreated(snapshotComputer(), userId);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index e15e8a8..75e1803f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -16,9 +16,11 @@
package com.android.server.pm;
+import java.util.concurrent.locks.ReentrantLock;
+
/**
* This is a unique class that is used as the PackageManager lock. It can be targeted for lock
* injection, similar to {@link ActivityManagerGlobalLock}.
*/
-public class PackageManagerTracedLock {
+public class PackageManagerTracedLock extends ReentrantLock {
}
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index c8c16db..f5dfb5c 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -4,7 +4,7 @@
flag {
name: "enable_early_screen_timeout_detector"
- namespace: "power_manager"
+ namespace: "power"
description: "Feature flag for Early Screen Timeout detector"
bug: "309861917"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
new file mode 100644
index 0000000..e597c3a
--- /dev/null
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "stats_flags",
+ package: "com.android.server.stats",
+ srcs: [
+ "stats_flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "stats_flags_lib",
+ aconfig_declarations: "stats_flags",
+}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
new file mode 100644
index 0000000..0de73a5
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+import android.app.ActivityManager;
+import android.app.StatsManager;
+import android.app.usage.NetworkStatsManager;
+import android.net.NetworkStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Aggregates Mobile Data Usage by process state per uid
+ */
+class AggregatedMobileDataStatsPuller {
+ private static final String TAG = "AggregatedMobileDataStatsPuller";
+
+ private static final boolean DEBUG = false;
+
+ private static class UidProcState {
+
+ private final int mUid;
+ private final int mState;
+
+ UidProcState(int uid, int state) {
+ mUid = uid;
+ mState = state;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UidProcState key)) return false;
+ return mUid == key.mUid && mState == key.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUid;
+ result = 31 * result + mState;
+ return result;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ }
+
+ private static class MobileDataStats {
+ private long mRxPackets = 0;
+ private long mTxPackets = 0;
+ private long mRxBytes = 0;
+ private long mTxBytes = 0;
+
+ public long getRxPackets() {
+ return mRxPackets;
+ }
+
+ public long getTxPackets() {
+ return mTxPackets;
+ }
+
+ public long getRxBytes() {
+ return mRxBytes;
+ }
+
+ public long getTxBytes() {
+ return mTxBytes;
+ }
+
+ public void addRxPackets(long rxPackets) {
+ mRxPackets += rxPackets;
+ }
+
+ public void addTxPackets(long txPackets) {
+ mTxPackets += txPackets;
+ }
+
+ public void addRxBytes(long rxBytes) {
+ mRxBytes += rxBytes;
+ }
+
+ public void addTxBytes(long txBytes) {
+ mTxBytes += txBytes;
+ }
+
+ public boolean isEmpty() {
+ return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0;
+ }
+ }
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<UidProcState, MobileDataStats> mUidStats;
+
+ private final SparseIntArray mUidPreviousState;
+
+ private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
+
+ private final NetworkStatsManager mNetworkStatsManager;
+
+ private final Handler mMobileDataStatsHandler;
+
+ AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+ mUidStats = new ArrayMap<>();
+ mUidPreviousState = new SparseIntArray();
+
+ mNetworkStatsManager = networkStatsManager;
+
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ }
+
+ HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
+ mMobileDataStatsHandlerThread.start();
+ mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+ }
+
+ public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
+ long unusedUptime) {
+ mMobileDataStatsHandler.post(
+ () -> {
+ noteUidProcessStateImpl(uid, state);
+ });
+ }
+
+ public int pullDataBytesTransfer(List<StatsEvent> data) {
+ synchronized (mLock) {
+ return pullDataBytesTransferLocked(data);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private MobileDataStats getUidStatsForPreviousStateLocked(int uid) {
+ final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+ if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) {
+ Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid "
+ + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN");
+ }
+
+ final UidProcState statsKey = new UidProcState(uid, previousState);
+ MobileDataStats stats;
+ if (mUidStats.containsKey(statsKey)) {
+ stats = mUidStats.get(statsKey);
+ } else {
+ stats = new MobileDataStats();
+ mUidStats.put(statsKey, stats);
+ }
+ return stats;
+ }
+
+ private void noteUidProcessStateImpl(int uid, int state) {
+ // noteUidProcessStateLocked can be called back to back several times while
+ // the updateNetworkStatsLocked loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ }
+ mUidPreviousState.put(uid, state);
+ }
+
+ private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
+ if (DEBUG) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
+ }
+ }
+
+ final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
+ if (isEmpty(latestStats)) {
+ if (DEBUG) {
+ Slog.w(TAG, "getMobileUidStats() failed");
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ return;
+ }
+ NetworkStats delta = latestStats.subtract(mLastMobileUidStats);
+ mLastMobileUidStats = latestStats;
+
+ if (!isEmpty(delta)) {
+ updateNetworkStatsDelta(delta);
+ } else if (DEBUG) {
+ Slog.w(TAG, "updateNetworkStats() no delta");
+ }
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ }
+
+ private void updateNetworkStatsDelta(NetworkStats delta) {
+ synchronized (mLock) {
+ for (NetworkStats.Entry entry : delta) {
+ if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+ continue;
+ }
+ MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) {
+ if (DEBUG) {
+ Slog.d(TAG, "pullDataBytesTransferLocked() start");
+ }
+ for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) {
+ if (!uidStats.getValue().isEmpty()) {
+ MobileDataStats stats = uidStats.getValue();
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE,
+ uidStats.getKey().getUid(),
+ ActivityManager.processStateAmToProto(uidStats.getKey().getState()),
+ stats.getRxBytes(),
+ stats.getRxPackets(),
+ stats.getTxBytes(),
+ stats.getTxPackets()));
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "pullDataBytesTransferLocked() done. results count " + pulledData.size());
+ }
+ if (!pulledData.isEmpty()) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ return StatsManager.PULL_SKIP;
+ }
+
+ private static boolean isEmpty(NetworkStats stats) {
+ long totalRxPackets = 0;
+ long totalTxPackets = 0;
+ for (NetworkStats.Entry entry : stats) {
+ if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+ continue;
+ }
+ totalRxPackets += entry.getRxPackets();
+ totalTxPackets += entry.getTxPackets();
+ // at least one non empty entry located
+ break;
+ }
+ final long totalPackets = totalRxPackets + totalTxPackets;
+ return totalPackets == 0;
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e876241..285bcc3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -59,6 +59,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -409,6 +410,15 @@
@GuardedBy("mKeystoreLock")
private IKeystoreMetrics mIKeystoreMetrics;
+ private AggregatedMobileDataStatsPuller mAggregatedMobileDataStatsPuller = null;
+
+ /**
+ * Whether or not to enable the new puller with aggregation by process state per uid on a
+ * system server side.
+ */
+ public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
+ addMobileBytesTransferByProcStatePuller();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -469,6 +479,20 @@
mContext = context;
}
+ private final class StatsPullAtomServiceInternalImpl extends StatsPullAtomServiceInternal {
+
+ @Override
+ public void noteUidProcessState(int uid, int state) {
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+ && mAggregatedMobileDataStatsPuller != null) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ mAggregatedMobileDataStatsPuller.noteUidProcessState(uid, state, elapsedRealtime,
+ uptime);
+ }
+ }
+ }
+
private native void initializeNativePullers();
/**
@@ -486,6 +510,11 @@
}
try {
switch (atomTag) {
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE:
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+ && mAggregatedMobileDataStatsPuller != null) {
+ return mAggregatedMobileDataStatsPuller.pullDataBytesTransfer(data);
+ }
case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
@@ -776,7 +805,10 @@
@Override
public void onStart() {
- // no op
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ LocalServices.addService(StatsPullAtomServiceInternal.class,
+ new StatsPullAtomServiceInternalImpl());
+ }
}
@Override
@@ -811,6 +843,9 @@
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+
+ initMobileDataStatsPuller();
+
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -972,6 +1007,18 @@
registerCachedAppsHighWatermarkPuller();
}
+ private void initMobileDataStatsPuller() {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = "
+ + ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER);
+ }
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ mAggregatedMobileDataStatsPuller =
+ new AggregatedMobileDataStatsPuller(mNetworkStatsManager);
+ }
+ }
+
private void initAndRegisterNetworkStatsPullers() {
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1013,6 +1060,9 @@
registerWifiBytesTransferBackground();
registerMobileBytesTransfer();
registerMobileBytesTransferBackground();
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ registerMobileBytesTransferByProcState();
+ }
registerBytesTransferByTagAndMetered();
registerDataUsageBytesTransfer();
registerOemManagedBytesTransfer();
@@ -1021,6 +1071,13 @@
}
}
+ private void registerMobileBytesTransferByProcState() {
+ final int tagId = FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE;
+ PullAtomMetadata metadata =
+ new PullAtomMetadata.Builder().setAdditiveFields(new int[] {3, 4, 5, 6}).build();
+ mStatsManager.setPullAtomCallback(tagId, metadata, DIRECT_EXECUTOR, mStatsCallbackImpl);
+ }
+
private void initAndRegisterDeferredPullers() {
mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
? mContext.getSystemService(UwbManager.class) : null;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
new file mode 100644
index 0000000..06adbfc
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+/**
+ * System-server internal interface to the {@link StatsPullAtomService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StatsPullAtomServiceInternal {
+
+ /**
+ * @param state Process state from ActivityManager.java.
+ */
+ public abstract void noteUidProcessState(int uid, int state);
+
+}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
new file mode 100644
index 0000000..5101a69
--- /dev/null
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.stats"
+
+flag {
+ name: "add_mobile_bytes_transfer_by_proc_state_puller"
+ namespace: "statsd"
+ description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
+ bug: "309512867"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 9c60fbb..d7b8495 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1038,7 +1038,7 @@
}
} finally {
if (surface != null) {
- // surface is not used in TvInteractiveAppManagerService.
+ // surface is not used in TvAdManagerService.
surface.release();
}
Binder.restoreCallingIdentity(identity);
@@ -1070,6 +1070,253 @@
@Override
public void startAdService(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "startAdService(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "startAdService");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).startAdService();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in start", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void stopAdService(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "stopAdService(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "stopAdService");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).stopAdService();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in stop", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void resetAdService(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "resetAdService(userId=%d)", userId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "resetAdService");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).resetAdService();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in reset", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendCurrentVideoBounds(IBinder sessionToken, Rect bounds, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentVideoBounds(bounds=%s)", bounds.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentVideoBounds");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendCurrentVideoBounds(bounds);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentVideoBounds", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentChannelUri");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendCurrentChannelUri(channelUri);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentChannelUri", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendTrackInfoList(IBinder sessionToken, List<TvTrackInfo> tracks, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendTrackInfoList(tracks=%s)", tracks.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendTrackInfoList");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendTrackInfoList(tracks);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendTrackInfoList", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCurrentTvInputId");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendCurrentTvInputId(inputId);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCurrentTvInputId", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void sendSigningResult(
+ IBinder sessionToken, String signingId, byte[] result, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendSigningResult(signingId=%s)", signingId);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendSigningResult");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).sendSigningResult(signingId, result);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendSigningResult", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId =
+ resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "notifyError");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyError(errMsg, params);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyError", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTvMessage(type=%d)", type);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyTvMessage");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyTvMessage(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTvMessage", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -1106,6 +1353,67 @@
}
}
+ @Override
+ public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createMediaView(windowToken, frame);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in createMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutMediaView(frame);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in relayoutMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeMediaView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeMediaView();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in removeMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
}
private final class BinderService extends ITvInteractiveAppManager.Stub {
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 106be5f..4cc2c02 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -48,6 +48,7 @@
import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
/**
* System service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -191,9 +192,23 @@
}
}
+ private void callPerUserServiceIfExist(
+ Consumer<WearableSensingManagerPerUserService> serviceConsumer,
+ RemoteCallback statusCallback) {
+ int userId = UserHandle.getCallingUserId();
+ synchronized (mLock) {
+ WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "Service not available for userId " + userId);
+ WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
+ WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ serviceConsumer.accept(service);
+ }
+ }
+
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
- final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
- UserHandle.getCallingUserId());
@Override
public void provideDataStream(
@@ -210,7 +225,9 @@
WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
- mService.onProvideDataStream(parcelFileDescriptor, callback);
+ callPerUserServiceIfExist(
+ service -> service.onProvideDataStream(parcelFileDescriptor, callback),
+ callback);
}
@Override
@@ -229,7 +246,9 @@
WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
- mService.onProvidedData(data, sharedMemory, callback);
+ callPerUserServiceIfExist(
+ service -> service.onProvidedData(data, sharedMemory, callback),
+ callback);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 036f7b6..3d492bb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -141,6 +141,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -991,6 +992,9 @@
private CustomAppTransition mCustomOpenTransition;
private CustomAppTransition mCustomCloseTransition;
+ /** Non-zero to pause dispatching configuration changes to the client. */
+ int mPauseConfigurationDispatchCount = 0;
+
private final Runnable mPauseTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -9276,6 +9280,59 @@
}
}
+ @Override
+ void dispatchConfigurationToChild(WindowState child, Configuration config) {
+ if (isConfigurationDispatchPaused()) {
+ return;
+ }
+ super.dispatchConfigurationToChild(child, config);
+ }
+
+ /**
+ * Pauses dispatch of configuration changes to the client. This includes any
+ * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
+ * a lifecycle change comes from another source (eg. stop), it will still run but will use the
+ * paused configuration.
+ *
+ * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
+ * That method is responsible for evaluating whether the activity needs to be relaunched and
+ * sending configurations.
+ */
+ void pauseConfigurationDispatch() {
+ ++mPauseConfigurationDispatchCount;
+ if (mPauseConfigurationDispatchCount == 1) {
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
+ + " %s", this);
+ }
+ }
+
+ /** @return `true` if configuration actually changed. */
+ boolean resumeConfigurationDispatch() {
+ --mPauseConfigurationDispatchCount;
+ if (mPauseConfigurationDispatchCount > 0) {
+ return false;
+ }
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
+ if (mPauseConfigurationDispatchCount < 0) {
+ Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
+ mPauseConfigurationDispatchCount = 0;
+ return false;
+ }
+ if (mLastReportedDisplayId == getDisplayId()
+ && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
+ return false;
+ }
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ dispatchConfigurationToChild(getChildAt(i), getConfiguration());
+ }
+ updateReportedConfigurationAndSend();
+ return true;
+ }
+
+ boolean isConfigurationDispatchPaused() {
+ return mPauseConfigurationDispatchCount > 0;
+ }
+
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9525,6 +9582,17 @@
return true;
}
+ if (isConfigurationDispatchPaused()) {
+ return true;
+ }
+
+ return updateReportedConfigurationAndSend();
+ }
+
+ boolean updateReportedConfigurationAndSend() {
+ if (isConfigurationDispatchPaused()) {
+ Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
+ }
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
+ "configuration: %s", this);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index bcb4559..9ac4a5c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -34,6 +34,7 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
import static com.android.window.flags.Flags.balShowToasts;
@@ -70,7 +71,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
-import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.util.HashMap;
@@ -275,10 +275,13 @@
@BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
checkedOptions.getPendingIntentBackgroundActivityStartMode();
- if (balRequireOptInByPendingIntentCreator() && originatingPendingIntent == null) {
- mAutoOptInReason = "notPendingIntent";
- } else if (balRequireOptInByPendingIntentCreator() && mIsCallForResult) {
+ if (!balImproveRealCallerVisibilityCheck()) {
+ // without this fix the auto-opt ins below would violate CTS tests
+ mAutoOptInReason = null;
+ } else if (mIsCallForResult) {
mAutoOptInReason = "callForResult";
+ } else if (originatingPendingIntent == null) {
+ mAutoOptInReason = "notPendingIntent";
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
mAutoOptInReason = "sameUid";
} else {
@@ -950,7 +953,7 @@
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY;
- if (Flags.balImproveRealCallerVisibilityCheck()) {
+ if (balImproveRealCallerVisibilityCheck()) {
if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "realCallingUid has visible window");
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7f785af..a1799b4 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -92,35 +92,39 @@
mRotation = rotation;
mWidth = w;
mHeight = h;
- final Rect unrestricted = mUnrestricted;
- unrestricted.set(0, 0, w, h);
- state.setDisplayFrame(unrestricted);
+ final Rect u = mUnrestricted;
+ u.set(0, 0, w, h);
+ state.setDisplayFrame(u);
state.setDisplayCutout(displayCutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
state.setDisplayShape(displayShape);
state.getDisplayCutoutSafe(safe);
- if (safe.left > unrestricted.left) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
- unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
+ if (safe.left > u.left) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout())
+ .setFrame(u.left, u.top, safe.left, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
}
- if (safe.top > unrestricted.top) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
- unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
+ if (safe.top > u.top) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout())
+ .setFrame(u.left, u.top, u.right, safe.top)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_TOP);
}
- if (safe.right < unrestricted.right) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
- safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
+ if (safe.right < u.right) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout())
+ .setFrame(safe.right, u.top, u.right, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
}
- if (safe.bottom < unrestricted.bottom) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
- unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
+ if (safe.bottom < u.bottom) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout())
+ .setFrame(u.left, safe.bottom, u.right, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 32d60c5..6a3cf43 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -469,8 +469,7 @@
case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
synchronized (mService.mGlobalLock) {
- mService.mTransactionFactory.get()
- .reparent((SurfaceControl) msg.obj, null).apply();
+ mService.mTransactionFactory.get().remove((SurfaceControl) msg.obj).apply();
}
break;
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index adbe3bc..d302f06 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -270,7 +270,7 @@
}
if (mSurfaceControl != null) {
if (!mRelinquishDragSurfaceToDropTarget && !relinquishDragSurfaceToDragSource()) {
- mTransaction.reparent(mSurfaceControl, null).apply();
+ mTransaction.remove(mSurfaceControl).apply();
} else {
mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 9d5ddf3..d9dda4a 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -62,6 +62,8 @@
*/
class InsetsSourceProvider {
+ private static final Rect EMPTY_RECT = new Rect();
+
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
protected WindowContainer mWindowContainer;
@@ -286,12 +288,15 @@
private void updateSourceFrameForServerVisibility() {
// Make sure we set the valid source frame only when server visible is true, because the
- // frame may not yet determined that server side doesn't think the window is ready to
+ // frame may not yet be determined that server side doesn't think the window is ready to
// visible. (i.e. No surface, pending insets that were given during layout, etc..)
- if (mServerVisible) {
- mSource.setFrame(mSourceFrame);
- } else {
- mSource.setFrame(0, 0, 0, 0);
+ final Rect frame = mServerVisible ? mSourceFrame : EMPTY_RECT;
+ if (mSource.getFrame().equals(frame)) {
+ return;
+ }
+ mSource.setFrame(frame);
+ if (mWindowContainer != null) {
+ mSource.updateSideHint(mWindowContainer.getBounds());
}
}
@@ -631,7 +636,7 @@
}
pw.print(prefix);
pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
- pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
+ pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition);
pw.println();
if (mWindowContainer != null) {
pw.print(prefix + "mWindowContainer=");
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6371bb4..0c6b174 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -369,6 +369,15 @@
*/
private boolean mMoveToBottomIfClearWhenLaunch;
+ /**
+ * If {@code true}, transitions are allowed even if this TaskFragment is empty. If
+ * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other
+ * conditions are met. Default to {@code false}.
+ *
+ * @see #isReadyToTransit
+ */
+ private boolean mAllowTransitionWhenEmpty;
+
/** When set, will force the task to report as invisible. */
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -509,6 +518,19 @@
mIsolatedNav = isolatedNav;
}
+ /**
+ * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+ * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+ * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+ * to {@code false}.
+ */
+ void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+ if (!isEmbedded()) {
+ return;
+ }
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ }
+
/** @see #mIsolatedNav */
boolean isIsolatedNav() {
return isEmbedded() && mIsolatedNav;
@@ -2827,8 +2849,9 @@
return true;
}
// We don't want to start the transition if the organized TaskFragment is empty, unless
- // it is requested to be removed.
- if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+ // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true.
+ if (getTopNonFinishingActivity() != null || mIsRemovalRequested
+ || mAllowTransitionWhenEmpty) {
return true;
}
// Organizer shouldn't change embedded TaskFragment in PiP.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bdea1bc..286182e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,7 @@
}
}
final InsetsSource source = new InsetsSource(id, provider.getType());
- source.setFrame(provider.getArbitraryRectangle());
+ source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 205ed97..4ba52e4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2202,6 +2202,7 @@
}
final TaskFragment taskFragment = new TaskFragment(mService,
creationParams.getFragmentToken(), true /* createdByOrganizer */);
+ taskFragment.setAllowTransitionWhenEmpty(creationParams.getAllowTransitionWhenEmpty());
// Set task fragment organizer immediately, since it might have to be notified about further
// actions.
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56f2bc3..7ad87ed 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5187,6 +5187,11 @@
if (mSurfaceControl == null) {
return;
}
+ if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
+ // Don't update surface-position while dispatch paused. This is calculated from
+ // the server-side activity configuration so return early.
+ return;
+ }
if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
&& !mSurfacePlacementNeeded) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5048cef..13e1ba78 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -639,9 +639,12 @@
@Override
void updateSurfacePosition(SurfaceControl.Transaction t) {
+ final ActivityRecord r = asActivityRecord();
+ if (r != null && r.isConfigurationDispatchPaused()) {
+ return;
+ }
super.updateSurfacePosition(t);
if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
- final ActivityRecord r = asActivityRecord();
final Task rootTask = r != null ? r.getRootTask() : null;
// Don't transform the activity in PiP because the PiP task organizer will handle it.
if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 2049331..8bc41af 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -281,6 +281,7 @@
void displayRemoved(JNIEnv* env, int32_t displayId);
void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
void setFocusedDisplay(int32_t displayId);
+ void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
void setPointerDisplayId(int32_t displayId);
@@ -1169,6 +1170,11 @@
mInputManager->getDispatcher().setFocusedDisplay(displayId);
}
+void NativeInputManager::setMinTimeBetweenUserActivityPokes(int64_t intervalMillis) {
+ mInputManager->getDispatcher().setMinTimeBetweenUserActivityPokes(
+ std::chrono::milliseconds(intervalMillis));
+}
+
void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
mInputManager->getDispatcher().setInputDispatchMode(enabled, frozen);
}
@@ -2122,6 +2128,13 @@
im->setFocusedDisplay(displayId);
}
+static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
+ jlong intervalMillis) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setMinTimeBetweenUserActivityPokes(intervalMillis);
+}
+
static void nativeRequestPointerCapture(JNIEnv* env, jobject nativeImplObj, jobject tokenObj,
jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2805,6 +2818,7 @@
{"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
(void*)nativeSetFocusedApplication},
{"setFocusedDisplay", "(I)V", (void*)nativeSetFocusedDisplay},
+ {"setMinTimeBetweenUserActivityPokes", "(J)V", (void*)nativeSetUserActivityPokeInterval},
{"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
{"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
{"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 6ab98fe..d0b290c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -31,6 +31,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <binder/IServiceManager.h>
+#include <com_android_input_flags.h>
#include <gui/SurfaceComposerClient.h>
#include <hardware_legacy/power.h>
#include <hidl/ServiceManagement.h>
@@ -109,10 +110,12 @@
eventTime = now;
}
- if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) {
- return;
+ if (!com::android::input::flags::rate_limit_user_activity_poke_in_dispatcher()) {
+ if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) {
+ return;
+ }
+ gLastEventTime[eventType] = eventTime;
}
- gLastEventTime[eventType] = eventTime;
// Tell the power HAL when user activity occurs.
setPowerBoost(Boost::INTERACTION, 0);
@@ -285,9 +288,11 @@
GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivityFromNative, clazz,
"userActivityFromNative", "(JIII)V");
- // Initialize
- for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
- gLastEventTime[i] = LLONG_MIN;
+ if (!com::android::input::flags::rate_limit_user_activity_poke_in_dispatcher()) {
+ // Initialize
+ for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
+ gLastEventTime[i] = LLONG_MIN;
+ }
}
gPowerManagerServiceObj = NULL;
return 0;
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index a0fb013..24d4952 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -94,7 +94,9 @@
isSystemUpdated: Boolean
) {
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ // The package may still be removed even if it was once notified as installed.
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
trimPermissionStates(packageState.appId)
}
}
@@ -245,6 +247,13 @@
flagMask: Int,
flagValues: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+ // sometimes get race conditions between that check and the actual mutateState() call.
+ // This should rarely happen but at least we should not crash.
+ Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+ return false
+ }
val oldFlags =
newState.userStates[userId]!!
.appIdDevicePermissionFlags[appId]
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
rename to services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index fd65807..a33e52f 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -25,23 +25,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.internal.inputmethod.SoftInputShowHideReason;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodManagerServiceTests {
+public final class InputMethodManagerServiceTests {
static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2;
static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3;
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
rename to services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
index a55d1c4..75118ea 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
@@ -22,14 +22,9 @@
import android.util.IntArray;
import androidx.annotation.NonNull;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
public final class InputMethodSettingsTest {
private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
@NonNull String initialEnabledImeStr, @NonNull String imeId,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
new file mode 100644
index 0000000..fcf761f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.IProcessObserver;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify that process events are dispatched to process observers.
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class ProcessObserverTest {
+ private static final String TAG = "ProcessObserverTest";
+
+ private static final String PACKAGE = "com.foo";
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInt;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInt;
+ @Mock
+ private BatteryStatsService mBatteryStatsService;
+
+ private ActivityManagerService mRealAms;
+ private ActivityManagerService mAms;
+
+ private ProcessList mRealProcessList = new ProcessList();
+ private ProcessList mProcessList;
+
+ final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class);
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+ doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+ mRealAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ mRealAms.mConstants.loadDeviceConfigConstants();
+ mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mRealAms.mAtmInternal = mActivityTaskManagerInt;
+ mRealAms.mPackageManagerInt = mPackageManagerInt;
+ mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+ mRealAms.mProcessesReady = true;
+ mAms = spy(mRealAms);
+ mRealProcessList.mService = mAms;
+ mProcessList = spy(mRealProcessList);
+
+ doReturn(mProcessObserver).when(mProcessObserver).asBinder();
+ mProcessList.registerProcessObserver(mProcessObserver);
+
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ return null;
+ }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mRealProcessList;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
+ }
+
+ private ProcessRecord makeActiveProcessRecord(String packageName)
+ throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai);
+ }
+
+ private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
+ throws Exception {
+ final IApplicationThread thread = mock(IApplicationThread.class);
+ final IBinder threadBinder = new Binder();
+ doReturn(threadBinder).when(thread).asBinder();
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting bindApplication() for "
+ + Arrays.toString(invocation.getArguments()));
+ if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+ mRealAms.finishAttachApplication(0);
+ }
+ return null;
+ }).when(thread).bindApplication(
+ any(), any(),
+ any(), any(), anyBoolean(),
+ any(), any(),
+ any(), any(),
+ any(),
+ any(), anyInt(),
+ anyBoolean(), anyBoolean(),
+ anyBoolean(), anyBoolean(), any(),
+ any(), any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ anyLong(), anyLong());
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+ r.setPid(myPid());
+ r.setStartUid(myUid());
+ r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+ r.makeActive(thread, mAms.mProcessStats);
+ doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+ anyBoolean());
+ return r;
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = packageName;
+ ai.uid = myUid();
+ return ai;
+ }
+
+ /**
+ * Verify that a process start event is dispatched to process observers.
+ */
+ @Test
+ public void testNormal() throws Exception {
+ ProcessRecord app = startProcess();
+ verify(mProcessObserver).onProcessStarted(
+ app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE);
+ }
+
+ private ProcessRecord startProcess() throws Exception {
+ final ProcessRecord app = makeActiveProcessRecord(PACKAGE);
+ final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+ mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+ /* expectedStartSeq */ 0, /* procAttached */ false);
+ app.getThread().bindApplication(PACKAGE, appInfo,
+ null, null, false,
+ null,
+ null,
+ null, null,
+ null,
+ null, 0,
+ false, false,
+ true, false,
+ null,
+ null, null,
+ null,
+ null, null, null,
+ null, null,
+ 0, 0);
+ return app;
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 654d7a8d..f49f638 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -44,6 +44,7 @@
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "ravenwood-junit",
],
libs: [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index ca162e0..ba2b538 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -32,6 +32,7 @@
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,8 @@
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
- private final MockBatteryStatsImpl mBatteryStats;
+ private final File mHistoryDir;
+ private MockBatteryStatsImpl mBatteryStats;
private Handler mHandler;
private BatteryUsageStats mBatteryUsageStats;
@@ -66,6 +68,10 @@
private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
+ private int mDisplayCount = -1;
+ private int mPerUidModemModel = -1;
+ private NetworkStats mNetworkStats;
+
public BatteryUsageStatsRule() {
this(0, null);
}
@@ -78,16 +84,38 @@
mHandler = mock(Handler.class);
mPowerProfile = spy(new PowerProfile());
mMockClock.currentTime = currentTime;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
- mBatteryStats.setPowerProfile(mPowerProfile);
+ mHistoryDir = historyDir;
+
+ if (!RavenwoodRule.isUnderRavenwood()) {
+ lateInitBatteryStats();
+ }
mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+ }
+
+ private void lateInitBatteryStats() {
+ if (mBatteryStats != null) return;
+
+ mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+ mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
mBatteryStats.onSystemReady();
+
+ if (mDisplayCount != -1) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
+ if (mPerUidModemModel != -1) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
+ }
+ if (mNetworkStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
public MockClock getMockClock() {
@@ -112,7 +140,10 @@
}
mCpusByPolicy.put(policy, relatedCpus);
mFreqsByPolicy.put(policy, frequencies);
- mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ if (mBatteryStats != null) {
+ mBatteryStats.setCpuScalingPolicies(
+ new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ }
return this;
}
@@ -174,13 +205,19 @@
public BatteryUsageStatsRule setNumDisplays(int value) {
when(mPowerProfile.getNumDisplays()).thenReturn(value);
- mBatteryStats.setDisplayCountLocked(value);
+ mDisplayCount = value;
+ if (mBatteryStats != null) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
return this;
}
public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
- synchronized (mBatteryStats) {
- mBatteryStats.setPerUidModemModel(perUidModemModel);
+ mPerUidModemModel = perUidModemModel;
+ if (mBatteryStats != null) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
}
return this;
}
@@ -210,7 +247,10 @@
}
public void setNetworkStats(NetworkStats networkStats) {
- mBatteryStats.setNetworkStats(networkStats);
+ mNetworkStats = networkStats;
+ if (mBatteryStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
@Override
@@ -225,6 +265,7 @@
}
private void before() {
+ lateInitBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0831086..8958fac 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,10 @@
"test-apps/SuspendTestApp/src/**/*.java",
],
+
+ kotlincflags: [
+ "-Werror",
+ ],
static_libs: [
"frameworks-base-testutils",
"services.accessibility",
@@ -78,6 +82,7 @@
"securebox",
"flag-junit",
"ravenwood-junit",
+ "net_flags_lib",
],
libs: [
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 88b2ed4..071db68 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -491,6 +492,22 @@
}
@Test
+ public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+ mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+ waitForIdle();
+ verify(mFaceService).registerAuthenticationStateListener(eq(listener));
+ }
+
+ @Test
public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
throws Exception {
setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3a3dd6e..f8b5b04 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -49,6 +50,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
@@ -58,6 +60,7 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -81,6 +84,8 @@
@SmallTest
public class FaceAuthenticationClientTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int USER_ID = 12;
private static final long OP_ID = 32;
private static final int WAKE_REASON = WakeReason.LIFT;
@@ -105,6 +110,8 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@@ -264,6 +271,29 @@
verify(mHal, never()).authenticate(anyInt());
}
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ false /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+ }
+
private FaceAuthenticationClient createClient() throws RemoteException {
return createClient(2 /* version */, mClientMonitorCallbackConverter,
false /* allowBackgroundAuthentication */,
@@ -311,7 +341,8 @@
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
mUsageStats, lockoutTracker, allowBackgroundAuthentication,
- null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
+ null /* sensorPrivacyManager */, 0 /* biometricStrength */,
+ mAuthenticationStateListeners) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 772ec8b..7648bd17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -51,6 +51,7 @@
import com.android.internal.R;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -89,6 +90,8 @@
private BiometricContext mBiometricContext;
@Mock
private BiometricStateCallback mBiometricStateCallback;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
private final TestLooper mLooper = new TestLooper();
private SensorProps[] mSensorProps;
@@ -119,8 +122,8 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
- mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
- mDaemon, new Handler(mLooper.getLooper()),
+ mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
+ mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@@ -154,7 +157,7 @@
final HidlFaceSensorConfig[] hidlFaceSensorConfig =
new HidlFaceSensorConfig[]{faceSensorConfig};
mFaceProvider = new FaceProvider(mContext,
- mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+ mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
mLockoutResetDispatcher, mBiometricContext, mDaemon,
new Handler(mLooper.getLooper()),
true /* resetLockoutRequiresChallenge */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index e558c4d..78c1e08 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -44,6 +44,7 @@
import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -81,6 +82,8 @@
private BiometricContext mBiometricContext;
@Mock
private BiometricStateCallback mBiometricStateCallback;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -116,8 +119,8 @@
Face10.sSystemClock = Clock.fixed(
Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
- mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
- mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
+ mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
+ sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 774ea5b..4ed6f74 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -451,6 +452,29 @@
}
@Test
+ public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+ }
+
+ @Test
public void cancelsAuthWhenNotInForeground() throws Exception {
final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5a62d92..5081198 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,8 @@
package com.android.server.locksettings;
+import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -30,25 +32,30 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.gatekeeper.GateKeeperResponse;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,6 +66,7 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -399,6 +407,60 @@
}
@Test
+ public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ mLocalService.registerLockSettingsStateListener(listener);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+
+ verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final LockscreenCredential badPassword = newPassword("badPassword");
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ mLocalService.registerLockSettingsStateListener(listener);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+
+ verify(listener).onAuthenticationFailed(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+ final LockscreenCredential password = newPassword("password");
+ setCredential(PRIMARY_USER_ID, password);
+ final LockscreenCredential badPassword = newPassword("badPassword");
+ final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+
+ mLocalService.registerLockSettingsStateListener(listener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+ verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+
+ mLocalService.unregisterLockSettingsStateListener(listener);
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+ .getResponseCode());
+ verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID);
+ }
+
+ @Test
public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
setUserSetupComplete(false);
setSecureFrpMode(true);
@@ -537,4 +599,12 @@
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
+
+ private ILockSettingsStateListener mockLockSettingsStateListener() {
+ ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class);
+ IBinder binder = mock(IBinder.class);
+ when(binder.isBinderAlive()).thenReturn(true);
+ when(listener.asBinder()).thenReturn(binder);
+ return listener;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 13dc120..d6d2b6d 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -327,12 +328,20 @@
isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+ // Background chain
+ final ArrayMap<Integer, Boolean> isRestrictedInBackground = new ArrayMap<>();
+ isRestrictedInBackground.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedInBackground.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedInBackground.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_BACKGROUND, isRestrictedInBackground);
+
final int[] chains = {
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_BACKGROUND
};
final int[] states = {
INetd.FIREWALL_RULE_ALLOW,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 2a76452..4451cae 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -26,12 +26,14 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -48,8 +50,13 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
+import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -64,6 +71,7 @@
import static android.net.NetworkTemplate.MATCH_CARRIER;
import static android.net.NetworkTemplate.MATCH_MOBILE;
import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -146,6 +154,8 @@
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
+import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.RemoteException;
@@ -153,6 +163,9 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -169,6 +182,7 @@
import android.util.Range;
import android.util.RecurrenceRule;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
@@ -243,6 +257,9 @@
public class NetworkPolicyManagerServiceTest {
private static final String TAG = "NetworkPolicyManagerServiceTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final long TEST_START = 1194220800000L;
private static final String TEST_IFACE = "test0";
private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
@@ -285,6 +302,7 @@
private @Mock TelephonyManager mTelephonyManager;
private @Mock UserManager mUserManager;
private @Mock NetworkStatsManager mStatsManager;
+ private @Mock PowerExemptionManager mPowerExemptionManager;
private TestDependencies mDeps;
private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
@@ -302,6 +320,7 @@
private NetworkPolicyManagerService mService;
private final ArraySet<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+ private BroadcastReceiver mPowerAllowlistReceiver;
/**
* In some of the tests while initializing NetworkPolicyManagerService,
@@ -446,6 +465,7 @@
@Before
public void callSystemReady() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())).thenReturn(new int[0]);
final Context context = InstrumentationRegistry.getContext();
@@ -482,6 +502,8 @@
return mUserManager;
case Context.NETWORK_STATS_SERVICE:
return mStatsManager;
+ case Context.POWER_EXEMPTION_SERVICE:
+ return mPowerExemptionManager;
default:
return super.getSystemService(name);
}
@@ -495,6 +517,9 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ if (filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)) {
+ mPowerAllowlistReceiver = receiver;
+ }
mRegisteredReceivers.add(receiver);
return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
}
@@ -2066,6 +2091,12 @@
expectHasUseRestrictedNetworksPermission(UID_A, true);
expectHasUseRestrictedNetworksPermission(UID_B, false);
+ // Set low enough proc-states to ensure these uids are allowed in the background chain.
+ // To maintain clean separation between separate firewall chains, the tests could
+ // check for the specific blockedReasons in the uidBlockedState.
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, 21);
+ callAndWaitOnUidStateChanged(UID_B, BACKGROUND_THRESHOLD_STATE - 1, 21);
+
Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
doAnswer(arg -> {
int[] uids = arg.getArgument(1);
@@ -2113,7 +2144,111 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainEnabled() throws Exception {
+ verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainOnProcStateChange() throws Exception {
+ // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+ clearInvocations(mNetworkManager);
+
+ mService.mBackgroundRestrictionDelayMs = 500; // To avoid waiting too long in tests.
+
+ // The app will be blocked when there is no prior proc-state.
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ int procStateSeq = 23;
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, procStateSeq++);
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_ALLOW);
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq++);
+
+ // The app should be blocked after a delay. Posting a message just after the delay and
+ // waiting for it to complete to ensure that the blocking code has executed.
+ waitForDelayedMessageOnHandler(mService.mBackgroundRestrictionDelayMs + 1);
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_DEFAULT);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainOnAllowlistChange() throws Exception {
+ // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+ clearInvocations(mNetworkManager);
+
+ // The apps will be blocked when there is no prior proc-state.
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ assertTrue(mService.isUidNetworkingBlocked(UID_B, false));
+
+ final int procStateSeq = 29;
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+ .thenReturn(new int[]{APP_ID_A, APP_ID_B});
+ final SparseIntArray firewallUidRules = new SparseIntArray();
+ doAnswer(arg -> {
+ final int[] uids = arg.getArgument(1);
+ final int[] rules = arg.getArgument(2);
+ assertTrue(uids.length == rules.length);
+
+ for (int i = 0; i < uids.length; ++i) {
+ firewallUidRules.put(uids[i], rules[i]);
+ }
+ return null;
+ }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_BACKGROUND),
+ any(int[].class), any(int[].class));
+
+ mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+ assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A, -1));
+ assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B, -1));
+
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+ assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testBackgroundChainOnTempAllowlistChange() throws Exception {
+ // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+ clearInvocations(mNetworkManager);
+
+ // The app will be blocked as is no prior proc-state.
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ final int procStateSeq = 19;
+ callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ final NetworkPolicyManagerInternal internal = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
+
+ internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_ALLOW);
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+ internal.onTempPowerSaveWhitelistChange(APP_ID_A, false, REASON_OTHER, "testing");
+
+ verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+ FIREWALL_RULE_DEFAULT);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ }
+
+ @Test
public void testLowPowerStandbyAllowlist() throws Exception {
+ // Chain background is also enabled but these procstates are important enough to be exempt.
callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
callAndWaitOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
callAndWaitOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
@@ -2200,7 +2335,21 @@
ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
- // TODO: test more combinations of blocked reasons.
+
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_NOT_IN_BACKGROUND), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+ | BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_NOT_IN_BACKGROUND),
+ BLOCKED_REASON_BATTERY_SAVER);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+ | BLOCKED_REASON_DOZE, ALLOWED_REASON_NOT_IN_BACKGROUND),
+ BLOCKED_REASON_DOZE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS), BLOCKED_REASON_APP_BACKGROUND);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_POWER_SAVE_ALLOWLIST), BLOCKED_REASON_NONE);
+ effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+ ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST), BLOCKED_REASON_NONE);
for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
final int expectedEffectiveBlockedReasons = test.getValue();
@@ -2529,7 +2678,6 @@
private FutureIntent mRestrictBackgroundChanged;
private void postMsgAndWaitForCompletion() throws InterruptedException {
- final Handler handler = mService.getHandlerForTesting();
final CountDownLatch latch = new CountDownLatch(1);
mService.getHandlerForTesting().post(latch::countDown);
if (!latch.await(5, TimeUnit.SECONDS)) {
@@ -2537,6 +2685,14 @@
}
}
+ private void waitForDelayedMessageOnHandler(long delayMs) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.getHandlerForTesting().postDelayed(latch::countDown, delayMs);
+ if (!latch.await(delayMs + 5_000, TimeUnit.MILLISECONDS)) {
+ fail("Timed out waiting for delayed msg to be handled");
+ }
+ }
+
private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
throws InterruptedException {
mService.setSubscriptionPlans(subId, plans, 0, callingPackage);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 10f27ca..72fa949 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -80,7 +80,7 @@
@BeforeClass
@JvmStatic
fun checkAllCasesUniquelyNamed() {
- val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
+ val duplicateCaseNames = CASES.mapIndexed { _, testCase ->
testCase.failures.map {
makeTestName(testCase, it.first, Params.Type.FAILURE)
} + testCase.allowed.map {
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
index 150822b..c07c4d7 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
@@ -18,12 +18,13 @@
import android.content.Context
import android.util.Xml
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.SystemConfig
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
class SystemConfigNamedActorTest {
@@ -37,14 +38,11 @@
private const val PACKAGE_TWO = "com.test.actor.two"
}
- private val context: Context = InstrumentationRegistry.getContext()
+ private val context: Context = InstrumentationRegistry.getInstrumentation().context
@get:Rule
val tempFolder = TemporaryFolder(context.filesDir)
- @get:Rule
- val expected = ExpectedException.none()
-
private var uniqueCounter = 0
@Test
@@ -193,11 +191,9 @@
</config>
""".write()
- expected.expect(IllegalStateException::class.java)
- expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
+ val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+ assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " +
"for the android namespace is not allowed")
-
- assertPermissions()
}
@Test
@@ -217,11 +213,9 @@
</config>
""".write()
- expected.expect(IllegalStateException::class.java)
- expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
+ val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+ assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
" defined as both $PACKAGE_ONE and $PACKAGE_TWO")
-
- assertPermissions()
}
private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
@@ -230,5 +224,5 @@
private fun assertPermissions() = SystemConfig(false).apply {
val parser = Xml.newPullParser()
readPermissions(parser, tempFolder.root, 0)
- }. let { assertThat(it.namedActors) }
+ }.let { assertThat(it.namedActors) }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index bfd2df2d..e75afcc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -27,12 +27,13 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +44,7 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -59,7 +61,10 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.Color;
@@ -80,6 +85,7 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -100,6 +106,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -132,6 +139,8 @@
KeyguardManager mKeyguardManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@@ -171,11 +180,14 @@
private static final int CUSTOM_LIGHT_OFF = 10000;
private static final int MAX_VIBRATION_DELAY = 1000;
private static final float DEFAULT_VOLUME = 1.0f;
+ private BroadcastReceiver mAvalancheBroadcastReceiver;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
getContext().addMockSystemService(Vibrator.class, mVibrator);
+ getContext().addMockSystemService(PackageManager.class, mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -214,8 +226,9 @@
private void initAttentionHelper(TestableFlagResolver flagResolver) {
mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
- mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
- mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
+ mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAttentionHelper.onSystemReady();
mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
mAttentionHelper.setAudioManager(mAudioManager);
mAttentionHelper.setSystemReady(true);
@@ -226,6 +239,29 @@
mAttentionHelper.setScreenOn(false);
mAttentionHelper.setInCallStateOffHook(false);
mAttentionHelper.mNotificationPulseEnabled = true;
+
+ if (Flags.crossAppPoliteNotifications()) {
+ // Capture BroadcastReceiver for avalanche triggers
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(getContext(), atLeastOnce()).registerReceiverAsUser(
+ broadcastReceiverCaptor.capture(),
+ any(), intentFilterCaptor.capture(), any(), any());
+ List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+ List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+ assertThat(broadcastReceivers.size()).isAtLeast(1);
+ assertThat(intentFilters.size()).isAtLeast(1);
+ for (int i = 0; i < intentFilters.size(); i++) {
+ final IntentFilter filter = intentFilters.get(i);
+ if (filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ mAvalancheBroadcastReceiver = broadcastReceivers.get(i);
+ }
+ }
+ assertThat(mAvalancheBroadcastReceiver).isNotNull();
+ }
}
//
@@ -2040,7 +2076,7 @@
}
@Test
- public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
+ public void testBeepVolume_politeNotif_AvalancheStrategy() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2048,6 +2084,11 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
NotificationRecord r = getBeepyNotification();
// set up internal state
@@ -2078,7 +2119,8 @@
}
@Test
- public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+ public void testBeepVolume_politeNotif_AvalancheStrategy_ChannelHasUserSound()
+ throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2086,6 +2128,11 @@
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
NotificationRecord r = getBeepyNotification();
// set up internal state
@@ -2364,6 +2411,82 @@
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
+ @Test
+ public void testAvalancheStrategyTriggers() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ final int avalancheTimeoutMs = 100;
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT, avalancheTimeoutMs);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intents
+ for (String intentAction
+ : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ // Set the action and extras to trigger the avalanche strategy
+ Intent intent = new Intent(intentAction);
+ Pair<String, Boolean> extras =
+ NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+ .get(intentAction);
+ if (extras != null) {
+ intent.putExtra(extras.first, extras.second);
+ }
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isTrue();
+
+ // Wait for avalanche timeout
+ Thread.sleep(avalancheTimeoutMs + 1);
+
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+
+ @Test
+ public void testAvalancheStrategyTriggers_disabledExtras() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ for (String intentAction
+ : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ Intent intent = new Intent(intentAction);
+ Pair<String, Boolean> extras =
+ NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+ .get(intentAction);
+ // Test only for intents with extras
+ if (extras != null) {
+ // Set the action extras to NOT trigger the avalanche strategy
+ intent.putExtra(extras.first, !extras.second);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+ }
+
+ @Test
+ public void testAvalancheStrategyTriggers_nonAvalancheIntents() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ // Broadcast intents that are not avalanche triggers
+ final Set<String> notAvalancheTriggerIntents = Set.of(
+ Intent.ACTION_USER_ADDED,
+ Intent.ACTION_SCREEN_ON,
+ Intent.ACTION_POWER_CONNECTED
+ );
+ for (String intentAction : notAvalancheTriggerIntents) {
+ Intent intent = new Intent(intentAction);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2a89b02..31d6fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3722,6 +3722,68 @@
assertFalse(ar.moveFocusableActivityToTop("test"));
}
+ @Test
+ public void testPauseConfigDispatch() throws RemoteException {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+ TYPE_BASE_APPLICATION);
+ attrs.setTitle("AppWindow");
+ final TestWindowState appWindow = createWindowState(attrs, activity);
+ activity.addWindow(appWindow);
+
+ clearInvocations(mClientLifecycleManager);
+ clearInvocations(activity);
+
+ Configuration ro = activity.getRequestedOverrideConfiguration();
+ ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
+ activity.onRequestedOverrideConfigurationChanged(ro);
+ activity.ensureActivityConfiguration();
+ mWm.mRoot.performSurfacePlacement();
+
+ // policy will center the bounds, so just check for matching size here.
+ assertEquals(100, activity.getWindowConfiguration().getBounds().width());
+ assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+ // No scheduled transactions since it asked for a restart.
+ verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+ verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+ assertTrue(appWindow.mResizeReported);
+
+ // act like everything drew and went idle
+ appWindow.mResizeReported = false;
+ makeLastConfigReportedToClient(appWindow, true);
+
+ // Now pause dispatch and try to resize
+ activity.pauseConfigurationDispatch();
+
+ ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
+ activity.onRequestedOverrideConfigurationChanged(ro);
+ activity.ensureActivityConfiguration();
+ mWm.mRoot.performSurfacePlacement();
+
+ // Activity should get new config (core-side)
+ assertEquals(130, activity.getWindowConfiguration().getBounds().width());
+ // But windows should not get new config.
+ assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+ // The client shouldn't receive any changes
+ verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+ // and lastReported shouldn't be set.
+ verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+ // There should be no resize reported to client.
+ assertFalse(appWindow.mResizeReported);
+
+ // Now resume dispatch
+ activity.resumeConfigurationDispatch();
+ mWm.mRoot.performSurfacePlacement();
+
+ // Windows and client should now receive updates
+ verify(activity, times(2)).setLastReportedConfiguration(any(), any());
+ verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
+ assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
+ assertTrue(appWindow.mResizeReported);
+ }
+
private ICompatCameraControlCallback getCompatCameraControlCallback() {
return new ICompatCameraControlCallback.Stub() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6759eef..752dc5e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4794,6 +4794,7 @@
new WindowManager.LayoutParams(TYPE_STATUS_BAR);
final Binder owner = new Binder();
attrs.gravity = android.view.Gravity.TOP;
+ attrs.height = STATUS_BAR_HEIGHT;
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0 /* types */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 7c7e562..245b2c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -444,6 +444,10 @@
// Not ready if the task is still visible when the TaskFragment becomes empty.
doReturn(true).when(task).isVisibleRequested();
assertFalse(taskFragment.isReadyToTransit());
+
+ // Ready if the mAllowTransitionWhenEmpty flag is true.
+ taskFragment.setAllowTransitionWhenEmpty(true);
+ assertTrue(taskFragment.isReadyToTransit());
}
@Test
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index fe699af..a1407869 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,7 +21,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
@@ -31,7 +30,6 @@
import android.os.ResultReceiver;
import android.text.TextUtils;
-import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ICallControl;
import com.android.server.telecom.flags.Flags;
@@ -52,20 +50,13 @@
@SuppressLint("NotCloseable")
public final class CallControl {
private static final String TAG = CallControl.class.getSimpleName();
- private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
private final String mCallId;
private final ICallControl mServerInterface;
- private final PhoneAccountHandle mPhoneAccountHandle;
- private final ClientTransactionalServiceRepository mRepository;
/** @hide */
- public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
- @NonNull ClientTransactionalServiceRepository repository,
- @NonNull PhoneAccountHandle pah) {
+ public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
mCallId = callId;
mServerInterface = serverInterface;
- mRepository = repository;
- mPhoneAccountHandle = pah;
}
/**
@@ -97,16 +88,14 @@
*/
public void setActive(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.setActive(mCallId,
- new CallControlResultReceiver("setActive", executor, callback));
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.setActive(mCallId,
+ new CallControlResultReceiver("setActive", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -134,16 +123,12 @@
validateVideoState(videoState);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.answer(videoState, mCallId,
- new CallControlResultReceiver("answer", executor, callback));
+ try {
+ mServerInterface.answer(videoState, mCallId,
+ new CallControlResultReceiver("answer", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -165,16 +150,14 @@
*/
public void setInactive(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.setInactive(mCallId,
- new CallControlResultReceiver("setInactive", executor, callback));
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.setInactive(mCallId,
+ new CallControlResultReceiver("setInactive", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -213,15 +196,11 @@
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
validateDisconnectCause(disconnectCause);
- if (mServerInterface != null) {
- try {
- mServerInterface.disconnect(mCallId, disconnectCause,
- new CallControlResultReceiver("disconnect", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.disconnect(mCallId, disconnectCause,
+ new CallControlResultReceiver("disconnect", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -245,15 +224,13 @@
*/
public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.startCallStreaming(mCallId,
- new CallControlResultReceiver("startCallStreaming", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.startCallStreaming(mCallId,
+ new CallControlResultReceiver("startCallStreaming", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -281,15 +258,11 @@
Objects.requireNonNull(callEndpoint);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.requestCallEndpointChange(callEndpoint,
- new CallControlResultReceiver("endpointChange", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.requestCallEndpointChange(callEndpoint,
+ new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -313,20 +286,16 @@
* passed that details why the operation failed.
*/
@FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
- public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+ public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.setMuteState(isMuted,
- new CallControlResultReceiver("setMuteState", executor, callback));
+ try {
+ mServerInterface.setMuteState(isMuted,
+ new CallControlResultReceiver("requestMuteState", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -352,14 +321,10 @@
public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
Objects.requireNonNull(event);
Objects.requireNonNull(extras);
- if (mServerInterface != null) {
- try {
- mServerInterface.sendEvent(mCallId, event, extras);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.sendEvent(mCallId, event, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index a089f5c..63db297 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -580,6 +580,9 @@
mExtras = phoneAccount.getExtras();
mGroupId = phoneAccount.getGroupId();
mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
+ if (phoneAccount.hasSimultaneousCallingRestriction()) {
+ mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();
+ }
}
/**
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 71e9184..467e89c 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -208,8 +208,7 @@
if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
// create the interface object that the client will interact with
- CallControl control = new CallControl(callId, callControl, mRepository,
- mPhoneAccountHandle);
+ CallControl control = new CallControl(callId, callControl);
// give the client the object via the OR that was passed into addCall
pendingControl.onResult(control);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1badf67..a73c46b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9430,16 +9430,6 @@
"missed_incoming_call_sms_originator_string_array";
/**
- * String array of Apn Type configurations.
- * The entries should be of form "APN_TYPE_NAME:priority".
- * priority is an integer that is sorted from highest to lowest.
- * example: cbs:5
- *
- * @hide
- */
- public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
-
- /**
* Network capability priority for determine the satisfy order in telephony. The priority is
* from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
* This allows other short-lived requests like MMS requests to be established. Emergency request
@@ -10755,17 +10745,14 @@
TimeUnit.DAYS.toMillis(1));
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
new String[0]);
- sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
- "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
- "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
- });
// Do not modify the priority unless you know what you are doing. This will have significant
// impacts on the order of data network setup.
sDefaults.putStringArray(
KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
"eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
- "ims:40", "dun:30", "enterprise:20", "internet:20"
+ "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20",
+ "prioritize_bandwidth:20", "prioritize_latency:20"
});
sDefaults.putStringArray(
KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
@@ -10777,9 +10764,10 @@
// registration state changes) retry can still happen.
"permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
+ "-3|65543|65547|2252|2253|2254, retry_interval=2500",
- "capabilities=mms|supl|cbs, retry_interval=2000",
- "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
- + "5000|10000|15000|20000|40000|60000|120000|240000|"
+ "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+ "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+ + "prioritize_bandwidth|prioritize_latency, retry_interval="
+ + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
+ "600000|1200000|1800000, maximum_retries=20"
});
sDefaults.putStringArray(
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 45dd02c..f3f1838 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -56,6 +56,7 @@
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
+#include "link/FeatureFlagsFilter.h"
#include "link/Linkers.h"
#include "link/ManifestFixer.h"
#include "link/NoDefaultResourceRemover.h"
@@ -1987,6 +1988,19 @@
context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
+ FeatureFlagsFilterOptions flags_filter_options;
+ if (context_->GetMinSdkVersion() > SDK_UPSIDE_DOWN_CAKE) {
+ // For API version > U, PackageManager will dynamically read the flag values and disable
+ // manifest elements accordingly when parsing the manifest.
+ // For API version <= U, we remove disabled elements from the manifest with the filter.
+ flags_filter_options.remove_disabled_elements = false;
+ flags_filter_options.flags_must_have_value = false;
+ }
+ FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+ if (!flags_filter.Consume(context_, manifest_xml.get())) {
+ return 1;
+ }
+
// Override the package ID when it is "android".
if (context_->GetCompilationPackage() == "android") {
context_->SetPackageId(kAndroidPackageId);
@@ -2531,7 +2545,7 @@
}
for (const std::string& arg : all_feature_flags_args) {
- if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+ if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
return 1;
}
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 26713fd..dc18b1c 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -330,7 +330,11 @@
"should only be used together with the --static-lib flag.",
&options_.merge_only);
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
- AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_);
+ AddOptionalFlagList("--feature-flags",
+ "Specify the values of feature flags. The pairs in the argument\n"
+ "are separated by ',' and the name is separated from the value by '='.\n"
+ "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).",
+ &feature_flags_args_);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 7096f5c..9323f3b 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -16,11 +16,10 @@
#include "Link.h"
-#include <android-base/file.h>
-
-#include "AppInfo.h"
#include "Diagnostics.h"
#include "LoadedApk.h"
+#include "android-base/file.h"
+#include "android-base/stringprintf.h"
#include "test/Test.h"
using testing::Eq;
@@ -993,4 +992,213 @@
ASSERT_FALSE(Link(link_args, &diag));
}
+static void BuildSDKWithFeatureFlagAttr(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, android::IDiagnostics* diag) {
+ const std::string android_values =
+ R"(<resources>
+ <staging-public-group type="attr" first-id="0x01fe0063">
+ <public name="featureFlag" />
+ </staging-public-group>
+ <attr name="featureFlag" format="string" />
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=false");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be removed if flag is disabled
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=true");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if flag is enabled
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAtMostUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_UPSIDE_DOWN_CAKE);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=");
+
+ // Flags must have values if <= UDC
+ const std::string app_apk = GetTestPath("app.apk");
+ ASSERT_FALSE(Link(app_link_args.Build(app_apk), &diag));
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=false");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=true");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAfterUDC) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+ const std::string manifest_contents = android::base::StringPrintf(
+ R"(<uses-sdk android:minSdkVersion="%d" />"
+ <permission android:name="FOO" android:featureFlag="flag" />)",
+ SDK_CUR_DEVELOPMENT);
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .AddContents(manifest_contents)
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("-I", android_apk)
+ .AddParameter("--feature-flags", "flag=");
+
+ const std::string app_apk = GetTestPath("app.apk");
+ BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+ // Permission element should be kept if > UDC, regardless of flag value
+ auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+ auto apk_manifest = apk->GetManifest();
+ ASSERT_THAT(apk_manifest, NotNull());
+ auto root = apk_manifest->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 93c1b61..02e4beae 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -251,10 +251,13 @@
return false;
}
- for (StringPiece line : util::Tokenize(contents, ' ')) {
+ for (StringPiece line : util::Tokenize(contents, '\n')) {
line = util::TrimWhitespace(line);
- if (!line.empty()) {
- out_arglist->emplace_back(line);
+ for (StringPiece arg : util::Tokenize(line, ' ')) {
+ arg = util::TrimWhitespace(arg);
+ if (!arg.empty()) {
+ out_arglist->emplace_back(arg);
+ }
}
}
return true;
@@ -270,10 +273,13 @@
return false;
}
- for (StringPiece line : util::Tokenize(contents, ' ')) {
+ for (StringPiece line : util::Tokenize(contents, '\n')) {
line = util::TrimWhitespace(line);
- if (!line.empty()) {
- out_argset->emplace(line);
+ for (StringPiece arg : util::Tokenize(line, ' ')) {
+ arg = util::TrimWhitespace(arg);
+ if (!arg.empty()) {
+ out_argset->emplace(arg);
+ }
}
}
return true;
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index 6c38080..618a3e0 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -25,6 +25,9 @@
using ::android::base::StringPrintf;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
namespace aapt {
namespace file {
@@ -34,9 +37,11 @@
constexpr const char sTestDirSep = '/';
#endif
-class FilesTest : public ::testing::Test {
+class FilesTest : public TestDirectoryFixture {
public:
void SetUp() override {
+ TestDirectoryFixture::SetUp();
+
std::stringstream builder;
builder << "hello" << sDirSep << "there";
expected_path_ = builder.str();
@@ -66,6 +71,42 @@
EXPECT_EQ(expected_path_, base);
}
+TEST_F(FilesTest, AppendArgsFromFile) {
+ const std::string args_file = GetTestPath("args.txt");
+ WriteFile(args_file,
+ " \n"
+ "arg1 arg2 arg3 \n"
+ " arg4 arg5");
+ std::vector<std::string> args;
+ std::string error;
+ ASSERT_TRUE(AppendArgsFromFile(args_file, &args, &error));
+ EXPECT_THAT(args, ElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendArgsFromFile_InvalidFile) {
+ std::vector<std::string> args;
+ std::string error;
+ ASSERT_FALSE(AppendArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile) {
+ const std::string args_file = GetTestPath("args.txt");
+ WriteFile(args_file,
+ " \n"
+ "arg2 arg4 arg1 \n"
+ " arg5 arg3");
+ std::unordered_set<std::string> args;
+ std::string error;
+ ASSERT_TRUE(AppendSetArgsFromFile(args_file, &args, &error));
+ EXPECT_THAT(args, UnorderedElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile_InvalidFile) {
+ std::unordered_set<std::string> args;
+ std::string error;
+ ASSERT_FALSE(AppendSetArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
#ifdef _WIN32
TEST_F(FilesTest, WindowsMkdirsLongPath) {
// Creating directory paths longer than the Windows maximum path length (260 charatcers) should
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f..2f6a361 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
*/
package com.android.hoststubgen.nativesubstitution;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
public class SystemProperties_host {
+ private static final Object sLock = new Object();
+
+ /** Active system property values */
+ @GuardedBy("sLock")
+ private static Map<String, String> sValues;
+ /** Predicate tested to determine if a given key can be read. */
+ @GuardedBy("sLock")
+ private static Predicate<String> sKeyReadablePredicate;
+ /** Predicate tested to determine if a given key can be written. */
+ @GuardedBy("sLock")
+ private static Predicate<String> sKeyWritablePredicate;
+ /** Callback to trigger when values are changed */
+ @GuardedBy("sLock")
+ private static Runnable sChangeCallback;
+
+ /**
+ * Reverse mapping that provides a way back to an original key from the
+ * {@link System#identityHashCode(Object)} of {@link String#intern}.
+ */
+ @GuardedBy("sLock")
+ private static SparseArray<String> sKeyHandles = new SparseArray<>();
+
+ public static void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback) {
+ synchronized (sLock) {
+ sValues = Objects.requireNonNull(values);
+ sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+ sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+ sChangeCallback = Objects.requireNonNull(changeCallback);
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_reset$ravenwood() {
+ synchronized (sLock) {
+ sValues = null;
+ sKeyReadablePredicate = null;
+ sKeyWritablePredicate = null;
+ sChangeCallback = null;
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_set(String key, String val) {
+ synchronized (sLock) {
+ Objects.requireNonNull(key);
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (!sKeyWritablePredicate.test(key)) {
+ throw new IllegalArgumentException(
+ "Write access to system property '" + key + "' denied via RavenwoodRule");
+ }
+ if (key.startsWith("ro.") && sValues.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "System property '" + key + "' already defined once; cannot redefine");
+ }
+ if ((val == null) || val.isEmpty()) {
+ sValues.remove(key);
+ } else {
+ sValues.put(key, val);
+ }
+ sChangeCallback.run();
+ }
+ }
+
public static String native_get(String key, String def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ Objects.requireNonNull(key);
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (!sKeyReadablePredicate.test(key)) {
+ throw new IllegalArgumentException(
+ "Read access to system property '" + key + "' denied via RavenwoodRule");
+ }
+ return sValues.getOrDefault(key, def);
+ }
}
+
public static int native_get_int(String key, int def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Integer.parseInt(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static long native_get_long(String key, long def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Long.parseLong(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static boolean native_get_boolean(String key, boolean def) {
- throw new RuntimeException("Not implemented yet");
+ return parseBoolean(native_get(key, ""), def);
}
public static long native_find(String name) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (sValues.containsKey(name)) {
+ name = name.intern();
+ final int handle = System.identityHashCode(name);
+ sKeyHandles.put(handle, name);
+ return handle;
+ } else {
+ return 0;
+ }
+ }
}
+
public static String native_get(long handle) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get(sKeyHandles.get((int) handle), "");
+ }
}
+
public static int native_get_int(long handle, int def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_int(sKeyHandles.get((int) handle), def);
+ }
}
+
public static long native_get_long(long handle, long def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_long(sKeyHandles.get((int) handle), def);
+ }
}
+
public static boolean native_get_boolean(long handle, boolean def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_boolean(sKeyHandles.get((int) handle), def);
+ }
}
- public static void native_set(String key, String def) {
- throw new RuntimeException("Not implemented yet");
- }
+
public static void native_add_change_callback() {
- throw new RuntimeException("Not implemented yet");
+ // Ignored; callback always registered via init above
}
+
public static void native_report_sysprop_change() {
- throw new RuntimeException("Not implemented yet");
+ // Report through callback always registered via init above
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ sChangeCallback.run();
+ }
+ }
+
+ private static boolean parseBoolean(String val, boolean def) {
+ // Matches system/libbase/include/android-base/parsebool.h
+ if (val == null) return def;
+ switch (val) {
+ case "1":
+ case "on":
+ case "true":
+ case "y":
+ case "yes":
+ return true;
+ case "0":
+ case "false":
+ case "n":
+ case "no":
+ case "off":
+ return false;
+ default:
+ return def;
+ }
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732..76bac92 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@
private val classes: ClassNodes,
val aidlPolicy: FilterPolicyWithReason?,
val featureFlagsPolicy: FilterPolicyWithReason?,
+ val syspropsPolicy: FilterPolicyWithReason?,
fallback: OutputFilter
) : DelegatingFilter(fallback) {
override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@
if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
return featureFlagsPolicy
}
+ if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+ return syspropsPolicy
+ }
return super.getPolicyForClass(className)
}
}
@@ -57,3 +61,13 @@
|| className.endsWith("/FeatureFlagsImpl")
|| className.endsWith("/FakeFeatureFlagsImpl");
}
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+ // Matches template classes defined here:
+ // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+ return className.startsWith("android/sysprop/")
+ && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e3..7fdd944 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@
var aidlPolicy: FilterPolicyWithReason? = null
var featureFlagsPolicy: FilterPolicyWithReason? = null
+ var syspropsPolicy: FilterPolicyWithReason? = null
try {
BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@
featureFlagsPolicy =
policy.withReason("$FILTER_REASON (feature flags)")
}
+ SpecialClass.Sysprops -> {
+ if (syspropsPolicy != null) {
+ throw ParseException(
+ "Policy for sysprops already defined")
+ }
+ syspropsPolicy =
+ policy.withReason("$FILTER_REASON (sysprops)")
+ }
}
}
}
@@ -205,10 +214,10 @@
}
var ret: OutputFilter = imf
- if (aidlPolicy != null || featureFlagsPolicy != null) {
+ if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
log.d("AndroidHeuristicsFilter enabled")
ret = AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, imf)
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
}
return ret
}
@@ -218,6 +227,7 @@
NotSpecial,
Aidl,
FeatureFlags,
+ Sysprops,
}
private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@
when (className.lowercase()) {
":aidl" -> return SpecialClass.Aidl
":feature_flags" -> return SpecialClass.FeatureFlags
+ ":sysprops" -> return SpecialClass.Sysprops
}
throw ParseException("Invalid special class name \"$className\"")
}