Merge "TIAF API review: Setup Interactive App session" into tm-dev
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 66767e2..da429af 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -210,6 +210,8 @@
* on how frequently it can be scheduled. Only available (and automatically applied) to
* system alarms.
*
+ * <p>Note that alarms set with a {@link WorkSource} <b>do not</b> get this flag.
+ *
* @hide
*/
@UnsupportedAppUsage
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index a8dd752..dfa1442 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -548,7 +548,7 @@
out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
out.attribute(null, "bias", String.valueOf(jobStatus.getBias()));
- out.attribute(null, "priority", String.valueOf(jobStatus.getEffectivePriority()));
+ out.attribute(null, "priority", String.valueOf(jobStatus.getJob().getPriority()));
out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
if (jobStatus.getInternalFlags() != 0) {
out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 849354b..04f96de 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -78,6 +78,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
import android.net.NetworkScoreManager;
@@ -219,7 +220,8 @@
private static final int HEADLESS_APP_CHECK_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS;
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_SYSTEM_ONLY;
// To name the lock for stack traces
static class Lock {}
@@ -253,7 +255,7 @@
private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>();
/**
- * Set of system apps that are headless (don't have any declared activities, enabled or
+ * Set of system apps that are headless (don't have any "front door" activities, enabled or
* disabled). Presence in this map indicates that the app is a headless system app.
*/
@GuardedBy("mHeadlessSystemApps")
@@ -1942,7 +1944,7 @@
try {
PackageInfo pi = mPackageManager.getPackageInfoAsUser(
packageName, HEADLESS_APP_CHECK_FLAGS, userId);
- evaluateSystemAppException(pi);
+ maybeUpdateHeadlessSystemAppCache(pi);
} catch (PackageManager.NameNotFoundException e) {
synchronized (mHeadlessSystemApps) {
mHeadlessSystemApps.remove(packageName);
@@ -1950,19 +1952,31 @@
}
}
- /** Returns true if the exception status changed. */
- private boolean evaluateSystemAppException(@Nullable PackageInfo pkgInfo) {
+ /**
+ * Update the "headless system app" cache.
+ *
+ * @return true if the cache is updated.
+ */
+ private boolean maybeUpdateHeadlessSystemAppCache(@Nullable PackageInfo pkgInfo) {
if (pkgInfo == null || pkgInfo.applicationInfo == null
|| (!pkgInfo.applicationInfo.isSystemApp()
&& !pkgInfo.applicationInfo.isUpdatedSystemApp())) {
return false;
}
+ final Intent frontDoorActivityIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(pkgInfo.packageName);
+ List<ResolveInfo> res = mPackageManager.queryIntentActivitiesAsUser(frontDoorActivityIntent,
+ HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM);
+ return updateHeadlessSystemAppCache(pkgInfo.packageName, ArrayUtils.isEmpty(res));
+ }
+
+ private boolean updateHeadlessSystemAppCache(String packageName, boolean add) {
synchronized (mHeadlessSystemApps) {
- if (pkgInfo.activities == null || pkgInfo.activities.length == 0) {
- // Headless system app.
- return mHeadlessSystemApps.add(pkgInfo.packageName);
+ if (add) {
+ return mHeadlessSystemApps.add(packageName);
} else {
- return mHeadlessSystemApps.remove(pkgInfo.packageName);
+ return mHeadlessSystemApps.remove(packageName);
}
}
}
@@ -1999,20 +2013,45 @@
}
}
+ /** Returns the packages that have launcher icons. */
+ private Set<String> getSystemPackagesWithLauncherActivities() {
+ final Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER);
+ List<ResolveInfo> activities = mPackageManager.queryIntentActivitiesAsUser(intent,
+ HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM);
+ final ArraySet<String> ret = new ArraySet<>();
+ for (ResolveInfo ri : activities) {
+ ret.add(ri.activityInfo.packageName);
+ }
+ return ret;
+ }
+
/** Call on system boot to get the initial set of headless system apps. */
private void loadHeadlessSystemAppCache() {
- Slog.d(TAG, "Loading headless system app cache. appIdleEnabled=" + mAppIdleEnabled);
+ final long start = SystemClock.uptimeMillis();
final List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM);
+
+ final Set<String> systemLauncherActivities = getSystemPackagesWithLauncherActivities();
+
final int packageCount = packages.size();
for (int i = 0; i < packageCount; i++) {
- PackageInfo pkgInfo = packages.get(i);
- if (pkgInfo != null && evaluateSystemAppException(pkgInfo)) {
+ final PackageInfo pkgInfo = packages.get(i);
+ if (pkgInfo == null) {
+ continue;
+ }
+ final String pkg = pkgInfo.packageName;
+ final boolean isHeadLess = !systemLauncherActivities.contains(pkg);
+
+ if (updateHeadlessSystemAppCache(pkg, isHeadLess)) {
mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE,
- UserHandle.USER_SYSTEM, -1, pkgInfo.packageName)
+ UserHandle.USER_SYSTEM, -1, pkg)
.sendToTarget();
}
}
+ final long end = SystemClock.uptimeMillis();
+ Slog.d(TAG, "Loaded headless system app cache in " + (end - start) + " ms:"
+ + " appIdleEnabled=" + mAppIdleEnabled);
}
@Override
diff --git a/boot/hiddenapi/hiddenapi-max-target-o.txt b/boot/hiddenapi/hiddenapi-max-target-o.txt
index d3b5be9..3c16915 100644
--- a/boot/hiddenapi/hiddenapi-max-target-o.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-o.txt
@@ -32472,14 +32472,6 @@
Landroid/net/DhcpResults;->setServerAddress(Ljava/lang/String;)Z
Landroid/net/DhcpResults;->setVendorInfo(Ljava/lang/String;)V
Landroid/net/DhcpResults;->TAG:Ljava/lang/String;
-Landroid/net/EthernetManager;-><init>(Landroid/content/Context;Landroid/net/IEthernetManager;)V
-Landroid/net/EthernetManager;->mContext:Landroid/content/Context;
-Landroid/net/EthernetManager;->mHandler:Landroid/os/Handler;
-Landroid/net/EthernetManager;->mListeners:Ljava/util/ArrayList;
-Landroid/net/EthernetManager;->mService:Landroid/net/IEthernetManager;
-Landroid/net/EthernetManager;->mServiceListener:Landroid/net/IEthernetServiceListener$Stub;
-Landroid/net/EthernetManager;->MSG_AVAILABILITY_CHANGED:I
-Landroid/net/EthernetManager;->TAG:Ljava/lang/String;
Landroid/net/EventLogTags;-><init>()V
Landroid/net/EventLogTags;->NTP_FAILURE:I
Landroid/net/EventLogTags;->NTP_SUCCESS:I
@@ -32513,39 +32505,6 @@
Landroid/net/http/X509TrustManagerExtensions;->mDelegate:Lcom/android/org/conscrypt/TrustManagerImpl;
Landroid/net/http/X509TrustManagerExtensions;->mIsSameTrustConfiguration:Ljava/lang/reflect/Method;
Landroid/net/http/X509TrustManagerExtensions;->mTrustManager:Ljavax/net/ssl/X509TrustManager;
-Landroid/net/IEthernetManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/net/IEthernetManager$Stub$Proxy;->addListener(Landroid/net/IEthernetServiceListener;)V
-Landroid/net/IEthernetManager$Stub$Proxy;->getAvailableInterfaces()[Ljava/lang/String;
-Landroid/net/IEthernetManager$Stub$Proxy;->getConfiguration(Ljava/lang/String;)Landroid/net/IpConfiguration;
-Landroid/net/IEthernetManager$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/net/IEthernetManager$Stub$Proxy;->isAvailable(Ljava/lang/String;)Z
-Landroid/net/IEthernetManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/net/IEthernetManager$Stub$Proxy;->removeListener(Landroid/net/IEthernetServiceListener;)V
-Landroid/net/IEthernetManager$Stub$Proxy;->setConfiguration(Ljava/lang/String;Landroid/net/IpConfiguration;)V
-Landroid/net/IEthernetManager$Stub;-><init>()V
-Landroid/net/IEthernetManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IEthernetManager;
-Landroid/net/IEthernetManager$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/net/IEthernetManager$Stub;->TRANSACTION_addListener:I
-Landroid/net/IEthernetManager$Stub;->TRANSACTION_getAvailableInterfaces:I
-Landroid/net/IEthernetManager$Stub;->TRANSACTION_getConfiguration:I
-Landroid/net/IEthernetManager$Stub;->TRANSACTION_isAvailable:I
-Landroid/net/IEthernetManager$Stub;->TRANSACTION_removeListener:I
-Landroid/net/IEthernetManager$Stub;->TRANSACTION_setConfiguration:I
-Landroid/net/IEthernetManager;->addListener(Landroid/net/IEthernetServiceListener;)V
-Landroid/net/IEthernetManager;->getAvailableInterfaces()[Ljava/lang/String;
-Landroid/net/IEthernetManager;->getConfiguration(Ljava/lang/String;)Landroid/net/IpConfiguration;
-Landroid/net/IEthernetManager;->isAvailable(Ljava/lang/String;)Z
-Landroid/net/IEthernetManager;->removeListener(Landroid/net/IEthernetServiceListener;)V
-Landroid/net/IEthernetManager;->setConfiguration(Ljava/lang/String;Landroid/net/IpConfiguration;)V
-Landroid/net/IEthernetServiceListener$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/net/IEthernetServiceListener$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/net/IEthernetServiceListener$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/net/IEthernetServiceListener$Stub$Proxy;->onAvailabilityChanged(Ljava/lang/String;Z)V
-Landroid/net/IEthernetServiceListener$Stub;-><init>()V
-Landroid/net/IEthernetServiceListener$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IEthernetServiceListener;
-Landroid/net/IEthernetServiceListener$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/net/IEthernetServiceListener$Stub;->TRANSACTION_onAvailabilityChanged:I
-Landroid/net/IEthernetServiceListener;->onAvailabilityChanged(Ljava/lang/String;Z)V
Landroid/net/IIpConnectivityMetrics$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/net/IIpConnectivityMetrics$Stub$Proxy;->addNetdEventCallback(ILandroid/net/INetdEventCallback;)Z
Landroid/net/IIpConnectivityMetrics$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
diff --git a/core/api/current.txt b/core/api/current.txt
index 52c3966..1391408a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -113,8 +113,9 @@
field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
- field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
+ field @Deprecated public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
field public static final String MANAGE_WIFI_INTERFACES = "android.permission.MANAGE_WIFI_INTERFACES";
+ field public static final String MANAGE_WIFI_NETWORK_SELECTION = "android.permission.MANAGE_WIFI_NETWORK_SELECTION";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -141,7 +142,7 @@
field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
field public static final String READ_LOGS = "android.permission.READ_LOGS";
field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
- field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
+ field public static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
@@ -3335,7 +3336,7 @@
}
public class InputMethod {
- ctor protected InputMethod(@NonNull android.accessibilityservice.AccessibilityService);
+ ctor public InputMethod(@NonNull android.accessibilityservice.AccessibilityService);
method @Nullable public final android.accessibilityservice.InputMethod.AccessibilityInputConnection getCurrentInputConnection();
method @Nullable public final android.view.inputmethod.EditorInfo getCurrentInputEditorInfo();
method public final boolean getCurrentInputStarted();
@@ -4275,7 +4276,6 @@
method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle);
method public final void setMediaController(android.media.session.MediaController);
method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams);
- method public void setPreferDockBigOverlays(boolean);
method @Deprecated public final void setProgress(int);
method @Deprecated public final void setProgressBarIndeterminate(boolean);
method @Deprecated public final void setProgressBarIndeterminateVisibility(boolean);
@@ -4285,6 +4285,7 @@
method public final void setResult(int);
method public final void setResult(int, android.content.Intent);
method @Deprecated public final void setSecondaryProgress(int);
+ method public void setShouldDockBigOverlays(boolean);
method public void setShowWhenLocked(boolean);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
method public void setTitle(CharSequence);
@@ -4295,6 +4296,7 @@
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public boolean shouldDockBigOverlays();
method public boolean shouldShowRequestPermissionRationale(@NonNull String);
method public boolean shouldUpRecreateTask(android.content.Intent);
method public boolean showAssist(android.os.Bundle);
@@ -18818,6 +18820,7 @@
method public void onStartInput(android.view.inputmethod.EditorInfo, boolean);
method public void onStartInputView(android.view.inputmethod.EditorInfo, boolean);
method public boolean onStartStylusHandwriting();
+ method public void onStylusHandwritingMotionEvent(@NonNull android.view.MotionEvent);
method public void onUnbindInput();
method @Deprecated public void onUpdateCursor(android.graphics.Rect);
method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
@@ -26063,11 +26066,9 @@
public static final class AppLinkInfo.Builder {
ctor public AppLinkInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.media.tv.interactive.AppLinkInfo build();
- method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setClassName(@NonNull String);
- method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setPackageName(@NonNull String);
- method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriHost(@Nullable String);
- method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriPrefix(@Nullable String);
- method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriScheme(@Nullable String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriHost(@NonNull String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriPrefix(@NonNull String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriScheme(@NonNull String);
}
public final class TvInteractiveAppInfo implements android.os.Parcelable {
@@ -26507,14 +26508,6 @@
method public int getUid();
}
- public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
- ctor public EthernetNetworkSpecifier(@NonNull String);
- method public int describeContents();
- method @Nullable public String getInterfaceName();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
- }
-
public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile {
method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms();
method public int getMaxMtu();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 241e5c8..a32ebe4 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -102,6 +102,7 @@
public abstract class PackageManager {
method @NonNull public String getPermissionControllerPackageName();
method @NonNull public String getSdkSandboxPackageName();
+ field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH";
field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
}
@@ -231,22 +232,6 @@
package android.net {
- public class EthernetManager {
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
- method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
- method public void setIncludeTestInterfaces(boolean);
- field public static final int ROLE_CLIENT = 1; // 0x1
- field public static final int ROLE_NONE = 0; // 0x0
- field public static final int ROLE_SERVER = 2; // 0x2
- field public static final int STATE_ABSENT = 0; // 0x0
- field public static final int STATE_LINK_DOWN = 1; // 0x1
- field public static final int STATE_LINK_UP = 2; // 0x2
- }
-
- public static interface EthernetManager.InterfaceStateListener {
- method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration);
- }
-
public class LocalSocket implements java.io.Closeable {
ctor public LocalSocket(@NonNull java.io.FileDescriptor);
}
@@ -454,6 +439,14 @@
}
+package android.telecom {
+
+ public abstract class ConnectionService extends android.app.Service {
+ method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
+ }
+
+}
+
package android.telephony {
public abstract class CellSignalStrength {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 06f1ac1..09d1114 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -34,6 +34,7 @@
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
+ field public static final String ALLOW_SLIPPERY_TOUCHES = "android.permission.ALLOW_SLIPPERY_TOUCHES";
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
@@ -469,7 +470,6 @@
method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions);
method @Deprecated public boolean isBackgroundVisibleBehind();
method @Deprecated public void onBackgroundVisibleBehindChanged(boolean);
- method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityAsUser(@NonNull android.content.Intent, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityForResultAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityForResultAsUser(@NonNull android.content.Intent, int, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityForResultAsUser(@NonNull android.content.Intent, @NonNull String, int, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
@@ -1650,13 +1650,13 @@
public final class SearchRequest implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public String getCallerPackageName();
method public float getMaxLatencyMillis();
method @NonNull public String getQuery();
method @NonNull public String getRequestId();
method public int getResultNumber();
method public int getResultOffset();
method @NonNull public android.os.Bundle getSearchConstraints();
- method @NonNull public String getSource();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "android.app.cloudsearch.IS_PRESUBMIT_SUGGESTION";
field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "android.app.cloudsearch.SEARCH_PROVIDER_FILTER";
@@ -1701,8 +1701,10 @@
method @NonNull public String getTitle();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.cloudsearch.SearchResult> CREATOR;
+ field public static final String EXTRAINFO_ACTION_APP_CARD = "android.app.cloudsearch.ACTION_APP_CARD";
field public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "android.app.cloudsearch.ACTION_BUTTON_IMAGE";
field public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "android.app.cloudsearch.ACTION_BUTTON_TEXT";
+ field public static final String EXTRAINFO_ACTION_INSTALL_BUTTON = "android.app.cloudsearch.ACTION_INSTALL_BUTTON";
field public static final String EXTRAINFO_APP_BADGES = "android.app.cloudsearch.APP_BADGES";
field public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER = "android.app.cloudsearch.APP_CONTAINS_ADS_DISCLAIMER";
field public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER = "android.app.cloudsearch.APP_CONTAINS_IAP_DISCLAIMER";
@@ -1710,6 +1712,8 @@
field public static final String EXTRAINFO_APP_DOMAIN_URL = "android.app.cloudsearch.APP_DOMAIN_URL";
field public static final String EXTRAINFO_APP_IARC = "android.app.cloudsearch.APP_IARC";
field public static final String EXTRAINFO_APP_ICON = "android.app.cloudsearch.APP_ICON";
+ field public static final String EXTRAINFO_APP_INSTALL_COUNT = "android.app.cloudsearch.APP_INSTALL_COUNT";
+ field public static final String EXTRAINFO_APP_PACKAGE_NAME = "android.app.cloudsearch.APP_PACKAGE_NAME";
field public static final String EXTRAINFO_APP_REVIEW_COUNT = "android.app.cloudsearch.APP_REVIEW_COUNT";
field public static final String EXTRAINFO_APP_SIZE_BYTES = "android.app.cloudsearch.APP_SIZE_BYTES";
field public static final String EXTRAINFO_APP_STAR_RATING = "android.app.cloudsearch.APP_STAR_RATING";
@@ -2948,6 +2952,7 @@
method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
field public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
@@ -6946,7 +6951,7 @@
}
public class Lnb implements java.lang.AutoCloseable {
- method public void addCallback(@NonNull android.media.tv.tuner.LnbCallback, @NonNull java.util.concurrent.Executor);
+ method public void addCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
method public void close();
method public boolean removeCallback(@NonNull android.media.tv.tuner.LnbCallback);
method public int sendDiseqcMessage(@NonNull byte[]);
@@ -8521,45 +8526,6 @@
package android.net {
- public class EthernetManager {
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void connectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disconnectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
- }
-
- public static interface EthernetManager.TetheredInterfaceCallback {
- method public void onAvailable(@NonNull String);
- method public void onUnavailable();
- }
-
- public static class EthernetManager.TetheredInterfaceRequest {
- method public void release();
- }
-
- public final class EthernetNetworkManagementException extends java.lang.RuntimeException implements android.os.Parcelable {
- ctor public EthernetNetworkManagementException(@NonNull String);
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkManagementException> CREATOR;
- }
-
- public final class EthernetNetworkUpdateRequest implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public android.net.IpConfiguration getIpConfiguration();
- method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkUpdateRequest> CREATOR;
- }
-
- public static final class EthernetNetworkUpdateRequest.Builder {
- ctor public EthernetNetworkUpdateRequest.Builder();
- ctor public EthernetNetworkUpdateRequest.Builder(@NonNull android.net.EthernetNetworkUpdateRequest);
- method @NonNull public android.net.EthernetNetworkUpdateRequest build();
- method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setIpConfiguration(@NonNull android.net.IpConfiguration);
- method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
- }
-
public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public MatchAllNetworkSpecifier();
method public int describeContents();
@@ -9030,7 +8996,7 @@
method public void enableVerboseLogging(boolean);
method @NonNull public int[] getChannelsMhzForBand(int);
method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
- method public int getMaxNumScanSsids(@NonNull String);
+ method public int getMaxSsidsPerScan(@NonNull String);
method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int);
method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
method public void notifyCountryCodeChanged(@Nullable String);
@@ -9820,7 +9786,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
method public boolean isCloneProfile();
- method public boolean isCredentialSharedWithParent();
+ method public boolean isCredentialSharableWithParent();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isMediaSharedWithParent();
@@ -10332,6 +10298,7 @@
field public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
+ field public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
field public static final String NAMESPACE_OTA = "ota";
field public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service";
field public static final String NAMESPACE_PERMISSIONS = "permissions";
@@ -10581,7 +10548,6 @@
field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled";
field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
field public static final String DOZE_ALWAYS_ON = "doze_always_on";
- field public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
field public static final String HUSH_GESTURE_USED = "hush_gesture_used";
field public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled";
field public static final String LAST_SETUP_SHOWN = "last_setup_shown";
@@ -12218,7 +12184,6 @@
public abstract class ConnectionService extends android.app.Service {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
- method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
}
public abstract class InCallService extends android.app.Service {
@@ -13376,7 +13341,7 @@
}
public class TelephonyManager {
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addCarrierPrivilegesListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addCarrierPrivilegesListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) @WorkerThread public void bootstrapAuthenticationRequest(int, @NonNull android.net.Uri, @NonNull android.telephony.gba.UaSecurityProtocolIdentifier, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.BootstrapAuthenticationCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
@@ -13476,7 +13441,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
@@ -13526,6 +13492,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterCarrierPrivilegesCallback(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
method public void updateServiceLocation();
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
@@ -13631,8 +13598,13 @@
field public static final int RESULT_SUCCESS = 0; // 0x0
}
- public static interface TelephonyManager.CarrierPrivilegesListener {
- method public void onCarrierPrivilegesChanged(@NonNull java.util.List<java.lang.String>, @NonNull int[]);
+ public static interface TelephonyManager.CarrierPrivilegesCallback {
+ method public void onCarrierPrivilegesChanged(@NonNull java.util.Set<java.lang.String>, @NonNull java.util.Set<java.lang.Integer>);
+ method public default void onCarrierServiceChanged(@Nullable String, int);
+ }
+
+ @Deprecated public static interface TelephonyManager.CarrierPrivilegesListener {
+ method @Deprecated public void onCarrierPrivilegesChanged(@NonNull java.util.List<java.lang.String>, @NonNull int[]);
}
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a22c4bc..2eb0f19 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -422,9 +422,9 @@
method @NonNull public android.content.res.Configuration getConfiguration();
method public int getParentTaskId();
method @Nullable public android.app.PictureInPictureParams getPictureInPictureParams();
- method public boolean getPreferDockBigOverlays();
method @NonNull public android.window.WindowContainerToken getToken();
method public boolean hasParentTask();
+ method public boolean shouldDockBigOverlays();
}
public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener {
@@ -622,7 +622,7 @@
package android.app.cloudsearch {
public static final class SearchRequest.Builder {
- method @NonNull public android.app.cloudsearch.SearchRequest.Builder setSource(@NonNull String);
+ method @NonNull public android.app.cloudsearch.SearchRequest.Builder setCallerPackageName(@NonNull String);
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c82f5f6..3cb04e7 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -825,17 +825,7 @@
for (int i = 0; i < mMagnificationControllers.size(); i++) {
mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
}
- AccessibilityServiceInfo info = getServiceInfo();
- if (info != null) {
- boolean requestIme = (info.flags
- & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
- if (requestIme && !mInputMethodInitialized) {
- mInputMethod = onCreateInputMethod();
- mInputMethodInitialized = true;
- }
- } else {
- Log.e(LOG_TAG, "AccessibilityServiceInfo is null in dispatchServiceConnected");
- }
+ updateInputMethod(getServiceInfo());
}
if (mSoftKeyboardController != null) {
mSoftKeyboardController.onServiceConnected();
@@ -846,6 +836,20 @@
onServiceConnected();
}
+ private void updateInputMethod(AccessibilityServiceInfo info) {
+ if (info != null) {
+ boolean requestIme = (info.flags
+ & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
+ if (requestIme && !mInputMethodInitialized) {
+ mInputMethod = onCreateInputMethod();
+ mInputMethodInitialized = true;
+ } else if (!requestIme & mInputMethodInitialized) {
+ mInputMethod = null;
+ mInputMethodInitialized = false;
+ }
+ }
+ }
+
/**
* This method is a part of the {@link AccessibilityService} lifecycle and is
* called after the system has successfully bound to the service. If is
@@ -2521,6 +2525,7 @@
*/
public final void setServiceInfo(AccessibilityServiceInfo info) {
mInfo = info;
+ updateInputMethod(info);
sendServiceInfo();
}
diff --git a/core/java/android/accessibilityservice/InputMethod.java b/core/java/android/accessibilityservice/InputMethod.java
index 001d804..36cfd0e 100644
--- a/core/java/android/accessibilityservice/InputMethod.java
+++ b/core/java/android/accessibilityservice/InputMethod.java
@@ -67,7 +67,7 @@
private InputConnection mStartedInputConnection;
private EditorInfo mInputEditorInfo;
- protected InputMethod(@NonNull AccessibilityService service) {
+ public InputMethod(@NonNull AccessibilityService service) {
mService = service;
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8f348a4..ec9bb26 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -984,6 +984,8 @@
private boolean mIsInMultiWindowMode;
private boolean mIsInPictureInPictureMode;
+ private boolean mShouldDockBigOverlays;
+
private UiTranslationController mUiTranslationController;
private SplashScreen mSplashScreen;
@@ -2977,13 +2979,28 @@
* <p> If specified, the system will try to respect the preference, but it may be
* overridden by a user preference.
*
- * @param preferDockBigOverlays indicates that the activity prefers big overlays to be
- * docked next to it instead of overlaying its content
+ * @param shouldDockBigOverlays indicates that big overlays should be docked next to the
+ * activity instead of overlay its content
*
* @see PictureInPictureParams.Builder#setExpandedAspectRatio
+ * @see #shouldDockBigOverlays
*/
- public void setPreferDockBigOverlays(boolean preferDockBigOverlays) {
- ActivityClient.getInstance().setPreferDockBigOverlays(mToken, preferDockBigOverlays);
+ public void setShouldDockBigOverlays(boolean shouldDockBigOverlays) {
+ ActivityClient.getInstance().setShouldDockBigOverlays(mToken, shouldDockBigOverlays);
+ mShouldDockBigOverlays = shouldDockBigOverlays;
+ }
+
+ /**
+ * Returns whether big overlays should be docked next to the activity as set by
+ * {@link #setShouldDockBigOverlays}.
+ *
+ * @return {@code true} if big overlays should be docked next to the activity instead
+ * of overlay its content
+ *
+ * @see #setShouldDockBigOverlays
+ */
+ public boolean shouldDockBigOverlays() {
+ return mShouldDockBigOverlays;
}
void dispatchMovedToDisplay(int displayId, Configuration config) {
@@ -5663,7 +5680,6 @@
* @throws ActivityNotFoundException
* @hide
*/
- @SystemApi
@RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
public void startActivityAsUser(@NonNull Intent intent,
@Nullable Bundle options, @NonNull UserHandle user) {
@@ -8249,6 +8265,7 @@
.getWindowingMode();
mIsInMultiWindowMode = inMultiWindowMode(windowingMode);
mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
+ mShouldDockBigOverlays = getResources().getBoolean(R.bool.config_dockBigOverlayWindows);
restoreHasCurrentPermissionRequest(icicle);
if (persistentState != null) {
onCreate(icicle, persistentState);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index cf8480c..7b7b1ef 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -324,9 +324,9 @@
}
}
- void setPreferDockBigOverlays(IBinder token, boolean preferDockBigOverlays) {
+ void setShouldDockBigOverlays(IBinder token, boolean shouldDockBigOverlays) {
try {
- getActivityClientController().setPreferDockBigOverlays(token, preferDockBigOverlays);
+ getActivityClientController().setShouldDockBigOverlays(token, shouldDockBigOverlays);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 64f0301..3d0ed20 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -868,6 +868,7 @@
String processName;
@UnsupportedAppUsage
ApplicationInfo appInfo;
+ String sdkSandboxClientAppPackage;
@UnsupportedAppUsage
List<ProviderInfo> providers;
ComponentName instrumentationName;
@@ -1113,9 +1114,9 @@
@Override
public final void bindApplication(String processName, ApplicationInfo appInfo,
- ProviderInfoList providerList, ComponentName instrumentationName,
- ProfilerInfo profilerInfo, Bundle instrumentationArgs,
- IInstrumentationWatcher instrumentationWatcher,
+ String sdkSandboxClientAppPackage, ProviderInfoList providerList,
+ ComponentName instrumentationName, ProfilerInfo profilerInfo,
+ Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
@@ -1155,6 +1156,7 @@
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
+ data.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
data.providers = providerList.getList();
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
@@ -3587,7 +3589,7 @@
}
try {
- Application app = r.packageInfo.makeApplication(false, mInstrumentation);
+ Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
@@ -4286,7 +4288,7 @@
BroadcastReceiver receiver;
ContextImpl context;
try {
- app = packageInfo.makeApplication(false, mInstrumentation);
+ app = packageInfo.makeApplicationInner(false, mInstrumentation);
context = (ContextImpl) app.getBaseContext();
if (data.info.splitName != null) {
context = (ContextImpl) context.createContextForSplit(data.info.splitName);
@@ -4475,7 +4477,7 @@
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
- Application app = packageInfo.makeApplication(false, mInstrumentation);
+ Application app = packageInfo.makeApplicationInner(false, mInstrumentation);
final java.lang.ClassLoader cl;
if (data.info.splitName != null) {
@@ -6536,6 +6538,9 @@
}
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ if (data.sdkSandboxClientAppPackage != null) {
+ data.info.setSdkSandboxStorage(data.sdkSandboxClientAppPackage);
+ }
if (agent != null) {
handleAttachAgent(agent, data.info);
@@ -6695,7 +6700,7 @@
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
- app = data.info.makeApplication(data.restrictedBackupMode, null);
+ app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
// Propagate autofill compat state
app.setAutofillOptions(data.autofillOptions);
@@ -7565,7 +7570,7 @@
mInstrumentation.basicInit(this);
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
- mInitialApplication = context.mPackageInfo.makeApplication(true, null);
+ mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7c7c7ef..4829dc0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2367,7 +2367,7 @@
null, // no permission for OP_WRITE_MEDIA_AUDIO
Manifest.permission.READ_MEDIA_VIDEO,
null, // no permission for OP_WRITE_MEDIA_VIDEO
- Manifest.permission.READ_MEDIA_IMAGE,
+ Manifest.permission.READ_MEDIA_IMAGES,
null, // no permission for OP_WRITE_MEDIA_IMAGES
null, // no permission for OP_LEGACY_STORAGE
null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 60e22f4..9bdddd0 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -360,6 +360,53 @@
*/
public static final int SUBREASON_FREEZER_BINDER_TRANSACTION = 20;
+ /**
+ * The process was killed because of force-stop, it could be due to that
+ * the user clicked the "Force stop" button of the application in the Settings;
+ * this would be set only when the reason is {@link #REASON_USER_REQUESTED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_FORCE_STOP = 21;
+
+ /**
+ * The process was killed because the user removed the application away from Recents;
+ * this would be set only when the reason is {@link #REASON_USER_REQUESTED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_REMOVE_TASK = 22;
+
+ /**
+ * The process was killed because the user stopped the application from the task manager;
+ * this would be set only when the reason is {@link #REASON_USER_REQUESTED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_STOP_APP = 23;
+
+ /**
+ * The process was killed because the user stopped the application from developer options,
+ * or via the adb shell commmand interface; this would be set only when the reason is
+ * {@link #REASON_USER_REQUESTED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_KILL_BACKGROUND = 24;
+
+ /**
+ * The process was killed because of package update; this would be set only when the reason is
+ * {@link #REASON_USER_REQUESTED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_PACKAGE_UPDATE = 25;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
@@ -520,6 +567,11 @@
SUBREASON_ISOLATED_NOT_NEEDED,
SUBREASON_FREEZER_BINDER_IOCTL,
SUBREASON_FREEZER_BINDER_TRANSACTION,
+ SUBREASON_FORCE_STOP,
+ SUBREASON_REMOVE_TASK,
+ SUBREASON_STOP_APP,
+ SUBREASON_KILL_BACKGROUND,
+ SUBREASON_PACKAGE_UPDATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SubReason {}
@@ -1193,6 +1245,16 @@
return "FREEZER BINDER IOCTL";
case SUBREASON_FREEZER_BINDER_TRANSACTION:
return "FREEZER BINDER TRANSACTION";
+ case SUBREASON_FORCE_STOP:
+ return "FORCE STOP";
+ case SUBREASON_REMOVE_TASK:
+ return "REMOVE TASK";
+ case SUBREASON_STOP_APP:
+ return "STOP APP";
+ case SUBREASON_KILL_BACKGROUND:
+ return "KILL BACKGROUND";
+ case SUBREASON_PACKAGE_UPDATE:
+ return "PACKAGE UPDATE";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a3dd705a..f5eb1f6 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1998,7 +1998,7 @@
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
String instanceName, Handler handler, Executor executor, UserHandle user) {
// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and
- // ActivityManagerLocal.bindSupplementalProcessService
+ // ActivityManagerLocal.bindSdkSandboxService
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index caf1c41b7..1307161 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -88,7 +88,7 @@
boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureParams params);
void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
- oneway void setPreferDockBigOverlays(in IBinder token, in boolean preferDockBigOverlays);
+ oneway void setShouldDockBigOverlays(in IBinder token, in boolean shouldDockBigOverlays);
void toggleFreeformWindowingMode(in IBinder token);
oneway void startLockTaskModeByToken(in IBinder token);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 77657d5..f4fbcce 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -72,6 +72,7 @@
@UnsupportedAppUsage
void scheduleStopService(IBinder token);
void bindApplication(in String packageName, in ApplicationInfo info,
+ in String sdkSandboxClientAppPackage,
in ProviderInfoList providerList, in ComponentName testName,
in ProfilerInfo profilerInfo, in Bundle testArguments,
IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index cf259e57..deefea8 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -37,6 +37,7 @@
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.FileUtils;
import android.os.GraphicsEnvironment;
import android.os.Handler;
@@ -411,6 +412,26 @@
}
}
+ /** @hide */
+ void setSdkSandboxStorage(String sdkSandboxClientAppPackage) {
+ int userId = UserHandle.myUserId();
+ mDeviceProtectedDataDirFile = Environment
+ .getDataMiscDeSharedSdkSandboxDirectory(userId, sdkSandboxClientAppPackage)
+ .getAbsoluteFile();
+ mCredentialProtectedDataDirFile = Environment
+ .getDataMiscCeSharedSdkSandboxDirectory(userId, sdkSandboxClientAppPackage)
+ .getAbsoluteFile();
+
+ if ((mApplicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ mDataDirFile = mDeviceProtectedDataDirFile;
+ } else {
+ mDataDirFile = mCredentialProtectedDataDirFile;
+ }
+ mDataDir = mDataDirFile.getAbsolutePath();
+ }
+
public static void makePaths(ActivityThread activityThread,
ApplicationInfo aInfo,
List<String> outZipPaths) {
@@ -1352,9 +1373,28 @@
return mResources;
}
+ /**
+ * This is for 3p apps accessing this hidden API directly... in which case, we don't return
+ * the cached Application instance.
+ */
@UnsupportedAppUsage
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
+ return makeApplicationInner(forceDefaultAppClass, instrumentation,
+ /* allowDuplicateInstances= */ true);
+ }
+
+ /**
+ * This is for all the (internal) callers, for which we do return the cached instance.
+ */
+ public Application makeApplicationInner(boolean forceDefaultAppClass,
+ Instrumentation instrumentation) {
+ return makeApplicationInner(forceDefaultAppClass, instrumentation,
+ /* allowDuplicateInstances= */ false);
+ }
+
+ private Application makeApplicationInner(boolean forceDefaultAppClass,
+ Instrumentation instrumentation, boolean allowDuplicateInstances) {
if (mApplication != null) {
return mApplication;
}
@@ -1366,11 +1406,15 @@
// Looks like this is always happening for the system server, because
// the LoadedApk created in systemMain() -> attach() isn't cached properly?
if (!"android".equals(mPackageName)) {
- Slog.wtf(TAG, "App instance already created for package=" + mPackageName
+ Slog.wtfStack(TAG, "App instance already created for package=" + mPackageName
+ " instance=" + cached);
}
- mApplication = cached;
- return cached;
+ if (!allowDuplicateInstances) {
+ mApplication = cached;
+ return cached;
+ }
+ // Some apps intentionally call makeApplication() to create a new Application
+ // instance... Sigh...
}
}
@@ -1421,8 +1465,10 @@
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
- synchronized (sApplications) {
- sApplications.put(mPackageName, app);
+ if (!allowDuplicateInstances) {
+ synchronized (sApplications) {
+ sApplications.put(mPackageName, app);
+ }
}
if (instrumentation != null) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index c6e36a3..89854bb 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -844,6 +844,24 @@
}
/**
+ * Sets an active {@link android.service.quicksettings.TileService} to listening state
+ *
+ * The {@code componentName}'s package must match the calling package.
+ *
+ * @param componentName the tile to set into listening state
+ * @see android.service.quicksettings.TileService#requestListeningState
+ * @hide
+ */
+ public void requestTileServiceListeningState(@NonNull ComponentName componentName) {
+ Objects.requireNonNull(componentName);
+ try {
+ getService().requestTileServiceListeningState(componentName, mContext.getUserId());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Request to the user to add a {@link android.service.quicksettings.TileService}
* to the set of current QS tiles.
* <p>
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 58db93c..bf6f634 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -138,8 +138,6 @@
import android.nearby.NearbyFrameworkInitializer;
import android.net.ConnectivityFrameworkInitializer;
import android.net.ConnectivityFrameworkInitializerTiramisu;
-import android.net.EthernetManager;
-import android.net.IEthernetManager;
import android.net.INetworkPolicyManager;
import android.net.IPacProxyManager;
import android.net.IVpnManager;
@@ -789,15 +787,6 @@
return new LowpanManager(ctx.getOuterContext(), service);
}});
- registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
- new CachedServiceFetcher<EthernetManager>() {
- @Override
- public EthernetManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.ETHERNET_SERVICE);
- IEthernetManager service = IEthernetManager.Stub.asInterface(b);
- return new EthernetManager(ctx.getOuterContext(), service);
- }});
-
registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
new CachedServiceFetcher<WifiNl80211Manager>() {
@Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 5c7c73c..1a38fcf 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -188,7 +188,7 @@
/**
* @hide
*/
- public boolean preferDockBigOverlays;
+ public boolean shouldDockBigOverlays;
/**
* The task id of the host Task of the launch-into-pip Activity, i.e., it points to the Task
@@ -392,8 +392,8 @@
/** @hide */
@TestApi
- public boolean getPreferDockBigOverlays() {
- return preferDockBigOverlays;
+ public boolean shouldDockBigOverlays() {
+ return shouldDockBigOverlays;
}
/** @hide */
@@ -465,7 +465,7 @@
&& displayAreaFeatureId == that.displayAreaFeatureId
&& Objects.equals(positionInParent, that.positionInParent)
&& Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
- && Objects.equals(preferDockBigOverlays, that.preferDockBigOverlays)
+ && Objects.equals(shouldDockBigOverlays, that.shouldDockBigOverlays)
&& Objects.equals(displayCutoutInsets, that.displayCutoutInsets)
&& getWindowingMode() == that.getWindowingMode()
&& Objects.equals(taskDescription, that.taskDescription)
@@ -522,7 +522,7 @@
token = WindowContainerToken.CREATOR.createFromParcel(source);
topActivityType = source.readInt();
pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
- preferDockBigOverlays = source.readBoolean();
+ shouldDockBigOverlays = source.readBoolean();
launchIntoPipHostTaskId = source.readInt();
displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
@@ -569,7 +569,7 @@
token.writeToParcel(dest, flags);
dest.writeInt(topActivityType);
dest.writeTypedObject(pictureInPictureParams, flags);
- dest.writeBoolean(preferDockBigOverlays);
+ dest.writeBoolean(shouldDockBigOverlays);
dest.writeInt(launchIntoPipHostTaskId);
dest.writeTypedObject(displayCutoutInsets, flags);
dest.writeTypedObject(topActivityInfo, flags);
@@ -610,7 +610,7 @@
+ " token=" + token
+ " topActivityType=" + topActivityType
+ " pictureInPictureParams=" + pictureInPictureParams
- + " preferDockBigOverlays=" + preferDockBigOverlays
+ + " shouldDockBigOverlays=" + shouldDockBigOverlays
+ " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+ " displayCutoutSafeInsets=" + displayCutoutInsets
+ " topActivityInfo=" + topActivityInfo
diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java
index 4d6507a..bf78325 100644
--- a/core/java/android/app/cloudsearch/SearchRequest.java
+++ b/core/java/android/app/cloudsearch/SearchRequest.java
@@ -100,7 +100,7 @@
*
*/
@NonNull
- private String mSource;
+ private String mCallerPackageName;
private SearchRequest(Parcel in) {
this.mQuery = in.readString();
@@ -109,17 +109,17 @@
this.mMaxLatencyMillis = in.readFloat();
this.mSearchConstraints = in.readBundle();
this.mId = in.readString();
- this.mSource = in.readString();
+ this.mCallerPackageName = in.readString();
}
private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis,
- Bundle searchConstraints, String source) {
+ Bundle searchConstraints, String callerPackageName) {
mQuery = query;
mResultOffset = resultOffset;
mResultNumber = resultNumber;
mMaxLatencyMillis = maxLatencyMillis;
mSearchConstraints = searchConstraints;
- mSource = source;
+ mCallerPackageName = callerPackageName;
}
/** Returns the original query. */
@@ -151,8 +151,8 @@
/** Gets the caller's package name. */
@NonNull
- public String getSource() {
- return mSource;
+ public String getCallerPackageName() {
+ return mCallerPackageName;
}
/** Returns the search request id, which is used to identify the request. */
@@ -169,8 +169,8 @@
*
* @hide
*/
- public void setSource(@NonNull String source) {
- this.mSource = source;
+ public void setCallerPackageName(@NonNull String callerPackageName) {
+ this.mCallerPackageName = callerPackageName;
}
private SearchRequest(Builder b) {
@@ -179,7 +179,7 @@
mResultNumber = b.mResultNumber;
mMaxLatencyMillis = b.mMaxLatencyMillis;
mSearchConstraints = requireNonNull(b.mSearchConstraints);
- mSource = requireNonNull(b.mSource);
+ mCallerPackageName = requireNonNull(b.mCallerPackageName);
}
/**
@@ -207,7 +207,7 @@
dest.writeFloat(this.mMaxLatencyMillis);
dest.writeBundle(this.mSearchConstraints);
dest.writeString(getRequestId());
- dest.writeString(this.mSource);
+ dest.writeString(this.mCallerPackageName);
}
@Override
@@ -231,7 +231,7 @@
&& mResultNumber == that.mResultNumber
&& mMaxLatencyMillis == that.mMaxLatencyMillis
&& Objects.equals(mSearchConstraints, that.mSearchConstraints)
- && Objects.equals(mSource, that.mSource);
+ && Objects.equals(mCallerPackageName, that.mCallerPackageName);
}
@Override
@@ -246,14 +246,15 @@
}
return String.format("SearchRequest: {query:%s,offset:%d;number:%d;max_latency:%f;"
- + "is_presubmit:%b;search_provider:%s;source:%s}", mQuery, mResultOffset,
- mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider, mSource);
+ + "is_presubmit:%b;search_provider:%s;callerPackageName:%s}", mQuery,
+ mResultOffset, mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider,
+ mCallerPackageName);
}
@Override
public int hashCode() {
return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
- mSearchConstraints, mSource);
+ mSearchConstraints, mCallerPackageName);
}
/**
@@ -268,7 +269,7 @@
private int mResultNumber;
private float mMaxLatencyMillis;
private Bundle mSearchConstraints;
- private String mSource;
+ private String mCallerPackageName;
/**
*
@@ -284,7 +285,7 @@
mResultNumber = 10;
mMaxLatencyMillis = 200;
mSearchConstraints = Bundle.EMPTY;
- mSource = "DEFAULT_CALLER";
+ mCallerPackageName = "DEFAULT_CALLER";
}
/** Sets the input query. */
@@ -329,8 +330,8 @@
*/
@NonNull
@TestApi
- public Builder setSource(@NonNull String source) {
- this.mSource = source;
+ public Builder setCallerPackageName(@NonNull String callerPackageName) {
+ this.mCallerPackageName = callerPackageName;
return this;
}
@@ -343,7 +344,7 @@
}
return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
- mSearchConstraints, mSource);
+ mSearchConstraints, mCallerPackageName);
}
}
}
diff --git a/core/java/android/app/cloudsearch/SearchResult.java b/core/java/android/app/cloudsearch/SearchResult.java
index af8adac..c6583b6 100644
--- a/core/java/android/app/cloudsearch/SearchResult.java
+++ b/core/java/android/app/cloudsearch/SearchResult.java
@@ -71,6 +71,10 @@
EXTRAINFO_APP_BADGES,
EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING,
EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING,
+ EXTRAINFO_ACTION_APP_CARD,
+ EXTRAINFO_ACTION_INSTALL_BUTTON,
+ EXTRAINFO_APP_PACKAGE_NAME,
+ EXTRAINFO_APP_INSTALL_COUNT,
EXTRAINFO_WEB_URL,
EXTRAINFO_WEB_ICON})
public @interface SearchResultExtraInfoKey {}
@@ -119,6 +123,20 @@
@SuppressLint("IntentName")
public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING =
"android.app.cloudsearch.ACTION_BUTTON_IMAGE";
+ /** Intent for tapping the app card, PendingIntent expected. */
+ @SuppressLint("IntentName")
+ public static final String EXTRAINFO_ACTION_APP_CARD =
+ "android.app.cloudsearch.ACTION_APP_CARD";
+ /** Intent for tapping the install button, PendingIntent expected. */
+ @SuppressLint("IntentName")
+ public static final String EXTRAINFO_ACTION_INSTALL_BUTTON =
+ "android.app.cloudsearch.ACTION_INSTALL_BUTTON";
+ /** App's package name, String value expected. */
+ public static final String EXTRAINFO_APP_PACKAGE_NAME =
+ "android.app.cloudsearch.APP_PACKAGE_NAME";
+ /** App's install count, double value expected. */
+ public static final String EXTRAINFO_APP_INSTALL_COUNT =
+ "android.app.cloudsearch.APP_INSTALL_COUNT";
/** Web content's URL, String value expected. */
public static final String EXTRAINFO_WEB_URL = "android.app.cloudsearch.WEB_URL";
/** Web content's domain icon, android.graphics.drawable.Icon expected. */
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 41b1a1f..cbb5183 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -28,6 +28,8 @@
import android.os.UserHandle;
import android.util.ArraySet;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -82,7 +84,7 @@
public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1;
private final int mLockState;
- private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
+ @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NonNull private final ArraySet<ComponentName> mAllowedActivities;
@NonNull private final ArraySet<ComponentName> mBlockedActivities;
@ActivityPolicy
@@ -94,10 +96,14 @@
@NonNull Set<ComponentName> allowedActivities,
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy) {
+ Preconditions.checkNotNull(usersWithMatchingAccounts);
+ Preconditions.checkNotNull(allowedActivities);
+ Preconditions.checkNotNull(blockedActivities);
+
mLockState = lockState;
mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
- mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities);
- mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities);
+ mAllowedActivities = new ArraySet<>(allowedActivities);
+ mBlockedActivities = new ArraySet<>(blockedActivities);
mDefaultActivityPolicy = defaultActivityPolicy;
}
@@ -130,30 +136,24 @@
}
/**
- * Returns the set of activities allowed to be streamed, or {@code null} if all activities are
+ * Returns the set of activities allowed to be streamed, or empty set if all activities are
* allowed, except the ones explicitly blocked.
*
* @see Builder#setAllowedActivities(Set)
*/
@NonNull
public Set<ComponentName> getAllowedActivities() {
- if (mAllowedActivities == null) {
- return Collections.emptySet();
- }
return Collections.unmodifiableSet(mAllowedActivities);
}
/**
- * Returns the set of activities that are blocked from streaming, or {@code null} to indicate
+ * Returns the set of activities that are blocked from streaming, or empty set to indicate
* that all activities in {@link #getAllowedActivities} are allowed.
*
* @see Builder#setBlockedActivities(Set)
*/
@NonNull
public Set<ComponentName> getBlockedActivities() {
- if (mBlockedActivities == null) {
- return Collections.emptySet();
- }
return Collections.unmodifiableSet(mBlockedActivities);
}
@@ -237,7 +237,7 @@
public static final class Builder {
private @LockState int mLockState = LOCK_STATE_DEFAULT;
- private Set<UserHandle> mUsersWithMatchingAccounts;
+ @NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();;
@NonNull private Set<ComponentName> mBlockedActivities = Collections.emptySet();
@NonNull private Set<ComponentName> mAllowedActivities = Collections.emptySet();
@ActivityPolicy
@@ -282,6 +282,7 @@
@NonNull
public Builder setUsersWithMatchingAccounts(
@NonNull Set<UserHandle> usersWithMatchingAccounts) {
+ Preconditions.checkNotNull(usersWithMatchingAccounts);
mUsersWithMatchingAccounts = usersWithMatchingAccounts;
return this;
}
@@ -301,6 +302,7 @@
*/
@NonNull
public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
+ Preconditions.checkNotNull(allowedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -327,6 +329,7 @@
*/
@NonNull
public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
+ Preconditions.checkNotNull(blockedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -343,9 +346,6 @@
*/
@NonNull
public VirtualDeviceParams build() {
- if (mUsersWithMatchingAccounts == null) {
- mUsersWithMatchingAccounts = Collections.emptySet();
- }
return new VirtualDeviceParams(
mLockState,
mUsersWithMatchingAccounts,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2bda020..60efb4d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2011,9 +2011,9 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
- @UnsupportedAppUsage
- public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
- UserHandle userId) {
+ @SystemApi
+ public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
+ @Nullable Bundle options, @NonNull UserHandle userId) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 410e106..148eacc 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -79,6 +79,11 @@
public boolean debuggable;
/**
+ * Indicates if this apk is a sdk.
+ */
+ public boolean isSdkLibrary;
+
+ /**
* Specifies the recommended install location. Can be one of
* {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
* {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 78dddb5..f9beaa7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4262,8 +4262,9 @@
* for more details.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String EXTRA_VERIFICATION_ROOT_HASH =
- "android.content.pm.extra.EXTRA_VERIFICATION_ROOT_HASH";
+ "android.content.pm.extra.VERIFICATION_ROOT_HASH";
/**
* Extra field name for the ID of a intent filter pending verification.
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 3f5c5d2..d94b0d8 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -312,7 +312,7 @@
* @hide
*/
public static boolean areExactMatch(Signature[] a, Signature[] b) {
- return (a.length == b.length) && ArrayUtils.containsAll(a, b)
+ return (ArrayUtils.size(a) == ArrayUtils.size(b)) && ArrayUtils.containsAll(a, b)
&& ArrayUtils.containsAll(b, a);
}
@@ -387,4 +387,4 @@
return sPrime;
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 5ffb958..269bec2 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -133,6 +133,11 @@
*/
private final boolean mHasDeviceAdminReceiver;
+ /**
+ * Indicates if this apk is a sdk.
+ */
+ private final boolean mIsSdkLibrary;
+
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
int versionCodeMajor, int revisionCode, int installLocation,
@@ -143,7 +148,7 @@
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -176,6 +181,7 @@
mTargetSdkVersion = targetSdkVersion;
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
+ mIsSdkLibrary = isSdkLibrary;
}
/**
@@ -473,11 +479,19 @@
return mHasDeviceAdminReceiver;
}
+ /**
+ * Indicates if this apk is a sdk.
+ */
+ @DataClass.Generated.Member
+ public boolean isIsSdkLibrary() {
+ return mIsSdkLibrary;
+ }
+
@DataClass.Generated(
- time = 1635266936769L,
+ time = 1643063342990L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 165cae8..5680bcd 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -87,6 +87,7 @@
private static final String TAG_USES_SDK = "uses-sdk";
private static final String TAG_USES_SPLIT = "uses-split";
private static final String TAG_MANIFEST = "manifest";
+ private static final String TAG_SDK_LIBRARY = "sdk-library";
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
@@ -449,6 +450,8 @@
boolean hasDeviceAdminReceiver = false;
+ boolean isSdkLibrary = false;
+
// Only search the tree when the tag is the direct child of <manifest> tag
int type;
final int searchDepth = parser.getDepth() + 1;
@@ -506,6 +509,8 @@
} else if (TAG_RECEIVER.equals(parser.getName())) {
hasDeviceAdminReceiver |= isDeviceAdminReceiver(
parser, hasBindDeviceAdminPermission);
+ } else if (TAG_SDK_LIBRARY.equals(parser.getName())) {
+ isSdkLibrary = true;
}
}
} else if (TAG_OVERLAY.equals(parser.getName())) {
@@ -598,7 +603,7 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver));
+ hasDeviceAdminReceiver, isSdkLibrary));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 8b86a16..a65b681 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -379,6 +379,30 @@
}
/**
+ * Computes the maxSdkVersion. If the package is not compatible with this platform, populates
+ * {@code outError[0]} with an error message.
+ * <p>
+ * {@code maxVers} is compared against {@code platformSdkVersion}. If {@code maxVers} is less
+ * than the {@code platformSdkVersion} then populates {@code outError[0]} with an error message.
+ * Otherwise, it returns {@code maxVers} unmodified.
+ *
+ * @param maxVers maxSdkVersion number, if specified in the application manifest, or {@code
+ * Integer.MAX_VALUE} otherwise
+ * @param platformSdkVersion platform SDK version number, typically Build.VERSION.SDK_INT
+ * @return the maxSdkVersion that was recognised or an error if the condition is not satisfied
+ */
+ public static ParseResult<Integer> computeMaxSdkVersion(@IntRange(from = 0) int maxVers,
+ @IntRange(from = 1) int platformSdkVersion, @NonNull ParseInput input) {
+ if (platformSdkVersion > maxVers) {
+ return input.error(PackageManager.INSTALL_FAILED_NEWER_SDK,
+ "Requires max SDK version " + maxVers + " but is "
+ + platformSdkVersion);
+ } else {
+ return input.success(maxVers);
+ }
+ }
+
+ /**
* Matches a given {@code targetCode} against a set of release codeNames. Target codes can
* either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form {@code
* [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}).
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index 5f5e812..e2789c9 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -105,6 +105,10 @@
* or locally compiled variants.
*/
private final boolean mUseEmbeddedDex;
+ /**
+ * Indicates if this package is a sdk.
+ */
+ private final boolean mIsSdkLibrary;
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
@@ -131,6 +135,7 @@
mRequiredSplitTypes = requiredSplitTypes;
mSplitRequired = (baseApk.isSplitRequired() || hasAnyRequiredSplitTypes());
mProfileableByShell = baseApk.isProfileableByShell();
+ mIsSdkLibrary = baseApk.isIsSdkLibrary();
mSplitNames = splitNames;
mSplitTypes = splitTypes;
mIsFeatureSplits = isFeatureSplits;
@@ -401,11 +406,20 @@
return mUseEmbeddedDex;
}
+ /**
+ * Indicates if this package is a sdk.
+ */
+ @DataClass.Generated.Member
+ public boolean isIsSdkLibrary() {
+ return mIsSdkLibrary;
+ }
+
@DataClass.Generated(
- time = 1628562559343L,
+ time = 1643132127068L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures =
+ "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index d9734b4..5981d27 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -781,10 +781,11 @@
* <li>The fpsMin and fpsMax will be a multiple 30fps.</li>
* <li>The fpsMin will be no less than 30fps, the fpsMax will be no less than 120fps.</li>
* <li>At least one range will be a fixed FPS range where fpsMin == fpsMax.</li>
- * <li>For each fixed FPS range, there will be one corresponding variable FPS range [30,
- * fps_max]. These kinds of FPS ranges are suitable for preview-only use cases where the
- * application doesn't want the camera device always produce higher frame rate than the display
- * refresh rate.</li>
+ * <li>For each fixed FPS range, there will be one corresponding variable FPS range
+ * [30, fps_max] or [60, fps_max]. These kinds of FPS ranges are suitable for preview-only
+ * use cases where the application doesn't want the camera device always produce higher frame
+ * rate than the display refresh rate. Both 30fps and 60fps preview rate will not be
+ * supported for the same recording rate.</li>
* </p>
*
* @return an array of supported high speed video recording FPS ranges The upper bound of
diff --git a/core/java/android/inputmethodservice/ImsConfigurationTracker.java b/core/java/android/inputmethodservice/ImsConfigurationTracker.java
index 3c78888..30ef0a2 100644
--- a/core/java/android/inputmethodservice/ImsConfigurationTracker.java
+++ b/core/java/android/inputmethodservice/ImsConfigurationTracker.java
@@ -63,8 +63,9 @@
*/
@MainThread
public void onBindInput(@Nullable Resources resources) {
- Preconditions.checkState(mInitialized,
- "onBindInput can be called only after onInitialize().");
+ if (!mInitialized) {
+ return;
+ }
if (mLastKnownConfig == null && resources != null) {
mLastKnownConfig = new Configuration(resources.getConfiguration());
}
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 499634a..8289c26 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -27,6 +27,8 @@
import android.os.IBinder;
import android.util.Slog;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.android.internal.policy.PhoneWindow;
@@ -40,6 +42,9 @@
private final WindowManager mWindowManager;
private boolean mIsViewAdded;
+ private View mInkView;
+ private InkVisibilityListener mInkViewVisibilityListener;
+ private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public InkWindow(@NonNull Context context) {
super(context);
@@ -102,4 +107,77 @@
lp.token = token;
setAttributes(lp);
}
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ if (mInkView == null) {
+ mInkView = view;
+ } else if (mInkView != view) {
+ throw new IllegalStateException("Only one Child Inking view is permitted.");
+ }
+ super.addContentView(view, params);
+ initInkViewVisibilityListener();
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ mInkView = view;
+ super.setContentView(view, params);
+ initInkViewVisibilityListener();
+ }
+
+ @Override
+ public void setContentView(View view) {
+ mInkView = view;
+ super.setContentView(view);
+ initInkViewVisibilityListener();
+ }
+
+ @Override
+ public void clearContentView() {
+ if (mGlobalLayoutListener != null && mInkView != null) {
+ mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+ mGlobalLayoutListener = null;
+ mInkView = null;
+ super.clearContentView();
+ }
+
+ /**
+ * Listener used by InkWindow to time the dispatching of {@link MotionEvent}s to Ink view, once
+ * it is visible to user.
+ */
+ interface InkVisibilityListener {
+ void onInkViewVisible();
+ }
+
+ void setInkViewVisibilityListener(InkVisibilityListener listener) {
+ mInkViewVisibilityListener = listener;
+ initInkViewVisibilityListener();
+ }
+
+ void initInkViewVisibilityListener() {
+ if (mInkView == null || mInkViewVisibilityListener == null
+ || mGlobalLayoutListener != null) {
+ return;
+ }
+ mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (mInkView.isVisibleToUser()) {
+ if (mInkViewVisibilityListener != null) {
+ mInkViewVisibilityListener.onInkViewVisible();
+ }
+ mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mGlobalLayoutListener = null;
+ }
+ }
+ };
+ mInkView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+
+ boolean isInkViewVisible() {
+ return getDecorView().getVisibility() == View.VISIBLE
+ && mInkView != null && mInkView.isVisibleToUser();
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 656aea1..b46bb32 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -143,6 +143,7 @@
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.util.RingBuffer;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.InlineSuggestionsRequestInfo;
@@ -334,6 +335,17 @@
"persist.sys.ime.can_render_gestural_nav_buttons";
/**
+ * Number of {@link MotionEvent} to buffer if IME is not ready with Ink view.
+ * This number may be configured eventually based on device's touch sampling frequency.
+ */
+ private static final int MAX_EVENTS_BUFFER = 500;
+
+ /**
+ * A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view.
+ **/
+ private RingBuffer<MotionEvent> mPendingEvents;
+
+ /**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
* the IME switcher button or not when the gestural navigation is enabled.
*
@@ -954,7 +966,8 @@
mInkWindow.show();
// deliver previous @param stylusEvents
- stylusEvents.forEach(mInkWindow.getDecorView()::dispatchTouchEvent);
+ stylusEvents.forEach(InputMethodService.this::onStylusHandwritingMotionEvent);
+
// create receiver for channel
mHandwritingEventReceiver = new SimpleBatchedInputEventReceiver(
channel,
@@ -963,11 +976,11 @@
if (!(event instanceof MotionEvent)) {
return false;
}
- return mInkWindow.getDecorView().dispatchTouchEvent((MotionEvent) event);
+ onStylusHandwritingMotionEvent((MotionEvent) event);
+ return true;
});
}
-
/**
* {@inheritDoc}
* @hide
@@ -2357,7 +2370,8 @@
*
* If the IME supports handwriting for the current input, it should return {@code true},
* ensure its inking views are attached to the {@link #getStylusHandwritingWindow()}, and handle
- * stylus input received on the ink window via {@link #getCurrentInputConnection()}.
+ * stylus input received from {@link #onStylusHandwritingMotionEvent(MotionEvent)} on the
+ * {@link #getStylusHandwritingWindow()} via {@link #getCurrentInputConnection()}.
* @return {@code true} if IME can honor the request, {@code false} if IME cannot at this time.
*/
public boolean onStartStylusHandwriting() {
@@ -2366,6 +2380,33 @@
}
/**
+ * Called after {@link #onStartStylusHandwriting()} returns {@code true} for every Stylus
+ * {@link MotionEvent}.
+ * By default, this method forwards all {@link MotionEvent}s to the
+ * {@link #getStylusHandwritingWindow()} once its visible, however IME can override it to
+ * receive them sooner.
+ * @param motionEvent {@link MotionEvent} from stylus.
+ */
+ public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) {
+ if (mInkWindow.isInkViewVisible()) {
+ mInkWindow.getDecorView().dispatchTouchEvent(motionEvent);
+ } else {
+ if (mPendingEvents == null) {
+ mPendingEvents = new RingBuffer(MotionEvent.class, MAX_EVENTS_BUFFER);
+ }
+ mPendingEvents.append(motionEvent);
+ mInkWindow.setInkViewVisibilityListener(() -> {
+ if (mPendingEvents != null && !mPendingEvents.isEmpty()) {
+ for (MotionEvent event : mPendingEvents.toArray()) {
+ mInkWindow.getDecorView().dispatchTouchEvent(event);
+ }
+ mPendingEvents.clear();
+ }
+ });
+ }
+ }
+
+ /**
* Called when the current stylus handwriting session was finished (either by the system or
* via {@link #finishStylusHandwriting()}.
*
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
index 3f26fa4..6b2db7d 100644
--- a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
@@ -67,7 +67,7 @@
}
};
- public ButtonDispatcher(int id) {
+ ButtonDispatcher(int id) {
mId = id;
}
@@ -125,8 +125,8 @@
public void setImageDrawable(KeyButtonDrawable drawable) {
mImageDrawable = drawable;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
if (mViews.get(i) instanceof ButtonInterface) {
((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable);
}
@@ -143,8 +143,8 @@
}
mVisibility = visibility;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setVisibility(mVisibility);
}
}
@@ -188,8 +188,8 @@
int nextAlpha = (int) (alpha * 255);
if (prevAlpha != nextAlpha) {
mAlpha = nextAlpha / 255f;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setAlpha(mAlpha);
}
}
@@ -198,8 +198,8 @@
public void setDarkIntensity(float darkIntensity) {
mDarkIntensity = darkIntensity;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
if (mViews.get(i) instanceof ButtonInterface) {
((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity);
}
@@ -208,8 +208,8 @@
public void setDelayTouchFeedback(boolean delay) {
mDelayTouchFeedback = delay;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
if (mViews.get(i) instanceof ButtonInterface) {
((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
}
@@ -218,55 +218,55 @@
public void setOnClickListener(View.OnClickListener clickListener) {
mClickListener = clickListener;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setOnClickListener(mClickListener);
}
}
public void setOnTouchListener(View.OnTouchListener touchListener) {
mTouchListener = touchListener;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setOnTouchListener(mTouchListener);
}
}
public void setLongClickable(boolean isLongClickable) {
mLongClickable = isLongClickable;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setLongClickable(mLongClickable);
}
}
public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
mLongClickListener = longClickListener;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setOnLongClickListener(mLongClickListener);
}
}
public void setOnHoverListener(View.OnHoverListener hoverListener) {
mOnHoverListener = hoverListener;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setOnHoverListener(mOnHoverListener);
}
}
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
mAccessibilityDelegate = delegate;
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
mViews.get(i).setAccessibilityDelegate(delegate);
}
}
public void setTranslation(int x, int y, int z) {
- final int N = mViews.size();
- for (int i = 0; i < N; i++) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; i++) {
final View view = mViews.get(i);
view.setTranslationX(x);
view.setTranslationY(y);
diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
index 4adc84b..4cfd813 100644
--- a/core/java/android/inputmethodservice/navigationbar/DeadZone.java
+++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
@@ -82,7 +82,7 @@
}
};
- public DeadZone(NavigationBarView view) {
+ DeadZone(NavigationBarView view) {
mNavigationBarView = view;
onConfigurationChanged(Surface.ROTATION_0);
}
@@ -92,13 +92,15 @@
}
private float getSize(long now) {
- if (mSizeMax == 0)
+ if (mSizeMax == 0) {
return 0;
+ }
long dt = (now - mLastPokeTime);
- if (dt > mHold + mDecay)
+ if (dt > mHold + mDecay) {
return mSizeMin;
- if (dt < mHold)
+ } else if (dt < mHold) {
return mSizeMax;
+ }
return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay);
}
@@ -177,8 +179,9 @@
private void poke(MotionEvent event) {
mLastPokeTime = event.getEventTime();
- if (DEBUG)
+ if (DEBUG) {
Log.v(TAG, "poked! size=" + getSize(mLastPokeTime));
+ }
if (mShouldFlash) mNavigationBarView.postInvalidate();
}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
index 25a443d..45c8a18 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
@@ -54,30 +54,30 @@
final class KeyButtonDrawable extends Drawable {
public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE =
- new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") {
- @Override
- public void setValue(KeyButtonDrawable drawable, float degree) {
- drawable.setRotation(degree);
- }
+ new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") {
+ @Override
+ public void setValue(KeyButtonDrawable drawable, float degree) {
+ drawable.setRotation(degree);
+ }
- @Override
- public Float get(KeyButtonDrawable drawable) {
- return drawable.getRotation();
- }
- };
+ @Override
+ public Float get(KeyButtonDrawable drawable) {
+ return drawable.getRotation();
+ }
+ };
public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y =
- new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") {
- @Override
- public void setValue(KeyButtonDrawable drawable, float y) {
- drawable.setTranslationY(y);
- }
+ new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") {
+ @Override
+ public void setValue(KeyButtonDrawable drawable, float y) {
+ drawable.setTranslationY(y);
+ }
- @Override
- public Float get(KeyButtonDrawable drawable) {
- return drawable.getTranslationY();
- }
- };
+ @Override
+ public Float get(KeyButtonDrawable drawable) {
+ return drawable.getTranslationY();
+ }
+ };
private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -100,7 +100,7 @@
}
};
- public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
+ KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
boolean horizontalFlip, Color ovalBackgroundColor) {
this(d, new ShadowDrawableState(lightColor, darkColor,
d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor));
@@ -433,8 +433,8 @@
final boolean mSupportsAnimation;
final Color mOvalBackgroundColor;
- public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
- boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) {
+ ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor, boolean animated,
+ boolean horizontalFlip, Color ovalBackgroundColor) {
mLightColor = lightColor;
mDarkColor = darkColor;
mSupportsAnimation = animated;
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
index 38a63b6..cf77c898 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
@@ -90,7 +90,7 @@
private Type mType = Type.ROUNDED_RECT;
- public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) {
+ KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) {
mMaxWidthResource = maxWidthResource;
mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource);
mTargetView = targetView;
@@ -126,7 +126,7 @@
private void drawSoftware(Canvas canvas) {
if (mGlowAlpha > 0f) {
final Paint p = getRipplePaint();
- p.setAlpha((int)(mGlowAlpha * 255f));
+ p.setAlpha((int) (mGlowAlpha * 255f));
final float w = getBounds().width();
final float h = getBounds().height();
@@ -412,7 +412,7 @@
mDrawingHardwareGlow = true;
setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
- getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+ getExtendSize() / 2 - GLOW_MAX_SCALE_FACTOR * getRippleSize() / 2);
startAnim.setDuration(ANIMATION_DURATION_SCALE);
startAnim.setInterpolator(mInterpolator);
startAnim.addListener(mAnimatorListener);
@@ -420,7 +420,7 @@
setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2));
final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(),
- getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+ getExtendSize() / 2 + GLOW_MAX_SCALE_FACTOR * getRippleSize() / 2);
endAnim.setDuration(ANIMATION_DURATION_SCALE);
endAnim.setInterpolator(mInterpolator);
endAnim.addListener(mAnimatorListener);
@@ -430,13 +430,13 @@
if (isHorizontal()) {
mTopProp = CanvasProperty.createFloat(0f);
mBottomProp = CanvasProperty.createFloat(getBounds().height());
- mRxProp = CanvasProperty.createFloat(getBounds().height()/2);
- mRyProp = CanvasProperty.createFloat(getBounds().height()/2);
+ mRxProp = CanvasProperty.createFloat(getBounds().height() / 2);
+ mRyProp = CanvasProperty.createFloat(getBounds().height() / 2);
} else {
mLeftProp = CanvasProperty.createFloat(0f);
mRightProp = CanvasProperty.createFloat(getBounds().width());
- mRxProp = CanvasProperty.createFloat(getBounds().width()/2);
- mRyProp = CanvasProperty.createFloat(getBounds().width()/2);
+ mRxProp = CanvasProperty.createFloat(getBounds().width() / 2);
+ mRyProp = CanvasProperty.createFloat(getBounds().width() / 2);
}
mGlowScale = GLOW_MAX_SCALE_FACTOR;
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
index 74d30f8..cfdb6ca 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -200,8 +200,8 @@
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
- x = (int)ev.getRawX();
- y = (int)ev.getRawY();
+ x = (int) ev.getRawX();
+ y = (int) ev.getRawY();
float slop = getQuickStepTouchSlopPx(getContext());
if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) {
@@ -272,12 +272,13 @@
: KeyButtonRipple.Type.ROUNDED_RECT);
}
+ @Override
public void playSoundEffect(int soundConstant) {
if (!mPlaySounds) return;
mAudioManager.playSoundEffect(soundConstant);
}
- public void sendEvent(int action, int flags) {
+ private void sendEvent(int action, int flags) {
sendEvent(action, flags, SystemClock.uptimeMillis());
}
@@ -309,8 +310,8 @@
switch (action) {
case KeyEvent.ACTION_DOWN:
handled = ims.onKeyDown(ev.getKeyCode(), ev);
- mTracking = handled && ev.getRepeatCount() == 0 &&
- (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
+ mTracking = handled && ev.getRepeatCount() == 0
+ && (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
break;
case KeyEvent.ACTION_UP:
handled = ims.onKeyUp(ev.getKeyCode(), ev);
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
index f01173e..a270675 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
@@ -59,4 +59,4 @@
}
return super.dispatchTouchEvent(event);
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
index d488890..e93bda2 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
@@ -121,7 +121,7 @@
return CONFIG_NAV_BAR_LAYOUT_HANDLE;
}
- public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
+ void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
mButtonDispatchers = buttonDispatchers;
for (int i = 0; i < buttonDispatchers.size(); i++) {
initiallyFill(buttonDispatchers.valueAt(i));
@@ -376,7 +376,7 @@
}
*/
- public static String extractSize(String buttonSpec) {
+ private static String extractSize(String buttonSpec) {
if (!buttonSpec.contains(SIZE_MOD_START)) {
return null;
}
@@ -384,7 +384,7 @@
return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
}
- public static String extractButton(String buttonSpec) {
+ private static String extractButton(String buttonSpec) {
if (!buttonSpec.contains(SIZE_MOD_START)) {
return buttonSpec;
}
@@ -398,9 +398,9 @@
mButtonDispatchers.valueAt(indexOfKey).addView(v);
}
if (v instanceof ViewGroup) {
- final ViewGroup viewGroup = (ViewGroup)v;
- final int N = viewGroup.getChildCount();
- for (int i = 0; i < N; i++) {
+ final ViewGroup viewGroup = (ViewGroup) v;
+ final int numChildViews = viewGroup.getChildCount();
+ for (int i = 0; i < numChildViews; i++) {
addToDispatchers(viewGroup.getChildAt(i));
}
}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index a2d7105..510b14e 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -48,8 +48,8 @@
* @hide
*/
public final class NavigationBarView extends FrameLayout {
- final static boolean DEBUG = false;
- final static String TAG = "NavBarView";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "NavBarView";
// Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN
private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
@@ -139,7 +139,7 @@
}
/**
- * Applies {@param consumer} to each of the nav bar views.
+ * Applies {@code consumer} to each of the nav bar views.
*/
public void forEachView(Consumer<View> consumer) {
if (mHorizontal != null) {
@@ -181,7 +181,7 @@
}
}
- public KeyButtonDrawable getBackDrawable() {
+ private KeyButtonDrawable getBackDrawable() {
KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back);
orientBackButton(drawable);
return drawable;
@@ -211,7 +211,7 @@
// Animate the back button's rotation to the new degrees and only in portrait move up the
// back button to line up with the other buttons
float targetY = useAltBack
- ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
+ ? -dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
: 0;
ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
@@ -233,6 +233,11 @@
super.setLayoutDirection(layoutDirection);
}
+ /**
+ * Updates the navigation icons based on {@code hints}.
+ *
+ * @param hints bit flags defined in {@link StatusBarManager}.
+ */
public void setNavigationIconHints(int hints) {
if (hints == mNavigationIconHints) return;
final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
@@ -250,15 +255,7 @@
updateNavButtonIcons();
}
- public void setDisabledFlags(int disabledFlags) {
- if (mDisabledFlags == disabledFlags) return;
-
- mDisabledFlags = disabledFlags;
-
- updateNavButtonIcons();
- }
-
- public void updateNavButtonIcons() {
+ private void updateNavButtonIcons() {
// We have to replace or restore the back and home button icons when exiting or entering
// carmode, respectively. Recents are not available in CarMode in nav bar so change
// to recent icon is not required.
@@ -319,7 +316,7 @@
mHorizontal.setVisibility(View.GONE);
}
- public void reorient() {
+ private void reorient() {
updateCurrentView();
final android.inputmethodservice.navigationbar.NavigationBarFrame frame =
@@ -372,6 +369,11 @@
}
}
+ /**
+ * Updates the dark intensity.
+ *
+ * @param intensity The intensity of darkness from {@code 0.0f} to {@code 1.0f}.
+ */
public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) {
for (int i = 0; i < mButtonDispatchers.size(); ++i) {
mButtonDispatchers.valueAt(i).setDarkIntensity(intensity);
diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
index 68163c3..9b36cc5 100644
--- a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
+++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
@@ -30,9 +30,8 @@
/**
* Automatically reverses the order of children as they are added.
* Also reverse the width and height values of layout params
- * @hide
*/
-public class ReverseLinearLayout extends LinearLayout {
+class ReverseLinearLayout extends LinearLayout {
/** If true, the layout is reversed vs. a regular linear layout */
private boolean mIsLayoutReverse;
@@ -40,7 +39,7 @@
/** If true, the layout is opposite to it's natural reversity from the layout direction */
private boolean mIsAlternativeOrder;
- public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@@ -129,7 +128,7 @@
public static class ReverseRelativeLayout extends RelativeLayout implements Reversible {
- public ReverseRelativeLayout(Context context) {
+ ReverseRelativeLayout(Context context) {
super(context);
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index de1dc80..5f9fdbf 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -5655,6 +5655,7 @@
.setMaxStatsAgeMs(0)
.includePowerModels()
.includeProcessStateData()
+ .includeVirtualUids()
.build());
stats.dump(pw, prefix);
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 37bd51b..b3f4d98 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -42,6 +42,7 @@
FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL,
FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY,
FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA,
+ FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BatteryUsageStatsFlags {}
@@ -69,6 +70,8 @@
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA = 0x0008;
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010;
+
private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
private final int mFlags;
@@ -271,6 +274,15 @@
}
/**
+ * Requests to return attribution data for virtual UIDs such as
+ * {@link Process#SDK_SANDBOX_VIRTUAL_UID}.
+ */
+ public Builder includeVirtualUids() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS;
+ return this;
+ }
+
+ /**
* Requests to aggregate stored snapshots between the two supplied timestamps
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 0a7a407..ecdc803 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -478,10 +478,20 @@
}
/** {@hide} */
+ public static File getDataMiscCeSharedSdkSandboxDirectory(int userId, String packageName) {
+ return buildPath(getDataMiscCeDirectory(userId), "sdksandbox", packageName, "shared");
+ }
+
+ /** {@hide} */
public static File getDataMiscDeDirectory(int userId) {
return buildPath(getDataDirectory(), "misc_de", String.valueOf(userId));
}
+ /** {@hide} */
+ public static File getDataMiscDeSharedSdkSandboxDirectory(int userId, String packageName) {
+ return buildPath(getDataMiscDeDirectory(userId), "sdksandbox", packageName, "shared");
+ }
+
private static File getDataProfilesDeDirectory(int userId) {
return buildPath(getDataDirectory(), "misc", "profiles", "cur", String.valueOf(userId));
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 010459d..9e47a70 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -293,7 +293,10 @@
*
* @return Returns the result from {@link Binder#onTransact}. A successful call
* generally returns true; false generally means the transaction code was not
- * understood.
+ * understood. For a oneway call to a different process false should never be
+ * returned. If a oneway call is made to code in the same process (usually to
+ * a C++ or Rust implementation), then there are no oneway semantics, and
+ * false can still be returned.
*/
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException;
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 39ca596..3cde031 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -109,7 +109,7 @@
boolean someUserHasAccount(in String accountName, in String accountType);
String getProfileType(int userId);
boolean isMediaSharedWithParent(int userId);
- boolean isCredentialSharedWithParent(int userId);
+ boolean isCredentialSharableWithParent(int userId);
boolean isDemoUser(int userId);
boolean isPreCreated(int userId);
UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index a1ff923..77d1498 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -202,20 +202,24 @@
private static final String PACKAGE_NAME_UNINITIALIZED = "";
private final BatteryStats.Uid mBatteryStatsUid;
private final int mUid;
+ private final boolean mIsVirtualUid;
private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED;
private boolean mExcludeFromBatteryUsageStats;
public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid) {
- super(data, CONSUMER_TYPE_UID);
- mBatteryStatsUid = batteryStatsUid;
- mUid = batteryStatsUid.getUid();
- data.putLong(COLUMN_INDEX_UID, mUid);
+ this(data, batteryStatsUid, batteryStatsUid.getUid());
}
public Builder(BatteryConsumerData data, int uid) {
+ this(data, null, uid);
+ }
+
+ private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid,
+ int uid) {
super(data, CONSUMER_TYPE_UID);
- mBatteryStatsUid = null;
+ mBatteryStatsUid = batteryStatsUid;
mUid = uid;
+ mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID;
data.putLong(COLUMN_INDEX_UID, mUid);
}
@@ -232,6 +236,10 @@
return mUid;
}
+ public boolean isVirtualUid() {
+ return mIsVirtualUid;
+ }
+
/**
* Sets the name of the package owned by this UID that consumed the highest amount
* of power since BatteryStats reset.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a715c69..276578e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4763,7 +4763,7 @@
}
/**
- * Returns {@code true} if the user shares lock settings credential with its parent user
+ * Returns whether the user can have shared lockscreen credential with its parent user.
*
* This API only works for {@link UserManager#isProfile() profiles}
* and will always return false for any other user type.
@@ -4776,9 +4776,9 @@
Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS})
@SuppressAutoDoc
- public boolean isCredentialSharedWithParent() {
+ public boolean isCredentialSharableWithParent() {
try {
- return mService.isCredentialSharedWithParent(mUserId);
+ return mService.isCredentialSharableWithParent(mUserId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index b501730..312abf8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -83,7 +83,6 @@
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
-import android.sysprop.VoldProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -1739,10 +1738,7 @@
* false not encrypted or file encrypted
*/
public static boolean isBlockEncrypted() {
- if (!isEncrypted()) {
- return false;
- }
- return RoSystemProperties.CRYPTO_BLOCK_ENCRYPTED;
+ return false;
}
/** {@hide}
@@ -1752,18 +1748,7 @@
* false not encrypted, file encrypted or default block encrypted
*/
public static boolean isNonDefaultBlockEncrypted() {
- if (!isBlockEncrypted()) {
- return false;
- }
-
- try {
- IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.getService("mount"));
- return storageManager.getPasswordType() != CRYPT_TYPE_DEFAULT;
- } catch (RemoteException e) {
- Log.e(TAG, "Error getting encryption type");
- return false;
- }
+ return false;
}
/** {@hide}
@@ -1777,8 +1762,7 @@
* framework, so no service needs to check for changes during their lifespan
*/
public static boolean isBlockEncrypting() {
- final String state = VoldProperties.encrypt_progress().orElse("");
- return !"".equalsIgnoreCase(state);
+ return false;
}
/** {@hide}
@@ -1793,8 +1777,7 @@
* framework, so no service needs to check for changes during their lifespan
*/
public static boolean inCryptKeeperBounce() {
- final String status = VoldProperties.decrypt().orElse("");
- return "trigger_restart_min_framework".equals(status);
+ return false;
}
/** {@hide} */
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 052e4d0..6982b3a 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -322,6 +322,13 @@
public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
/**
+ * Namespace for all OnDevicePersonalization related feature.
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
+
+ /**
* Namespace for features related to the Package Manager Service.
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cfef7ad..a6ad5e5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10729,14 +10729,6 @@
"communal_mode_trusted_networks";
/**
- * Setting to allow Fast Pair scans to be enabled.
- * @hide
- */
- @SystemApi
- @Readable
- public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
-
- /**
* Setting to store denylisted system languages by the CEC {@code <Set Menu Language>}
* confirmation dialog.
*
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 1fd75e6..001707d 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -231,7 +231,7 @@
* The default value for whether to show complications on the overlay.
* @hide
*/
- public static final boolean DEFAULT_SHOW_COMPLICATIONS = true;
+ public static final boolean DEFAULT_SHOW_COMPLICATIONS = false;
private final IDreamManager mDreamManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index b507328..0829d28 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -15,18 +15,19 @@
*/
package android.service.quicksettings;
-import android.Manifest;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Dialog;
import android.app.Service;
+import android.app.StatusBarManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -147,13 +148,6 @@
"android.service.quicksettings.TOGGLEABLE_TILE";
/**
- * Used to notify SysUI that Listening has be requested.
- * @hide
- */
- public static final String ACTION_REQUEST_LISTENING =
- "android.service.quicksettings.action.REQUEST_LISTENING";
-
- /**
* @hide
*/
public static final String EXTRA_SERVICE = "service";
@@ -482,14 +476,24 @@
*
* This method is only applicable to tiles that have {@link #META_DATA_ACTIVE_TILE} defined
* as true on their TileService Manifest declaration, and will do nothing otherwise.
+ *
+ * For apps targeting {@link Build.VERSION_CODES#TIRAMISU} or later, this call may throw
+ * the following exceptions if the request is not valid:
+ * <ul>
+ * <li> {@link NullPointerException} if {@code component} is {@code null}.</li>
+ * <li> {@link SecurityException} if the package of {@code component} does not match
+ * the calling package or if the calling user cannot act on behalf of the user from the
+ * {@code context}.</li>
+ * <li> {@link IllegalArgumentException} if the user of the {@code context} is not the
+ * current user.</li>
+ * </ul>
*/
public static final void requestListeningState(Context context, ComponentName component) {
- final ComponentName sysuiComponent = ComponentName.unflattenFromString(
- context.getResources().getString(
- com.android.internal.R.string.config_systemUIServiceComponent));
- Intent intent = new Intent(ACTION_REQUEST_LISTENING);
- intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component);
- intent.setPackage(sysuiComponent.getPackageName());
- context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
+ StatusBarManager sbm = context.getSystemService(StatusBarManager.class);
+ if (sbm == null) {
+ Log.e(TAG, "No StatusBarManager service found");
+ return;
+ }
+ sbm.requestTileServiceListeningState(component);
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index c1fcd66..f844592 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -27,6 +27,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.carrier.CarrierService;
import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DisconnectCauses;
@@ -36,6 +37,7 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.telephony.TelephonyManager.CarrierPrivilegesListener;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
@@ -44,17 +46,19 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.listeners.ListenerExecutor;
-import com.android.internal.telephony.ICarrierPrivilegesListener;
+import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
/**
* A centralized place to notify telephony related status changes, e.g, {@link ServiceState} update
@@ -1260,34 +1264,78 @@
pkgName, attributionTag, callback, new int[0], notifyNow);
}
- private static class CarrierPrivilegesListenerWrapper extends ICarrierPrivilegesListener.Stub
+ // TODO(b/216549778): Remove listener logic once all clients switch to CarrierPrivilegesCallback
+ private static class CarrierPrivilegesCallbackWrapper extends ICarrierPrivilegesCallback.Stub
implements ListenerExecutor {
- private final WeakReference<CarrierPrivilegesListener> mListener;
- private final Executor mExecutor;
+ // Either mListener or mCallback may be null, never both
+ @Nullable private final WeakReference<CarrierPrivilegesListener> mListener;
+ @Nullable private final WeakReference<CarrierPrivilegesCallback> mCallback;
+ @NonNull private final Executor mExecutor;
- CarrierPrivilegesListenerWrapper(CarrierPrivilegesListener listener, Executor executor) {
+ CarrierPrivilegesCallbackWrapper(
+ @NonNull CarrierPrivilegesCallback callback, @NonNull Executor executor) {
+ mListener = null;
+ mCallback = new WeakReference<>(callback);
+ mExecutor = executor;
+ }
+
+ CarrierPrivilegesCallbackWrapper(
+ @NonNull CarrierPrivilegesListener listener, @NonNull Executor executor) {
mListener = new WeakReference<>(listener);
+ mCallback = null;
mExecutor = executor;
}
@Override
public void onCarrierPrivilegesChanged(
- List<String> privilegedPackageNames, int[] privilegedUids) {
- Binder.withCleanCallingIdentity(
- () ->
- executeSafely(
- mExecutor,
- mListener::get,
- cpl ->
- cpl.onCarrierPrivilegesChanged(
- privilegedPackageNames, privilegedUids)));
+ @NonNull List<String> privilegedPackageNames, @NonNull int[] privilegedUids) {
+ if (mListener != null) {
+ Binder.withCleanCallingIdentity(
+ () ->
+ executeSafely(
+ mExecutor,
+ mListener::get,
+ cpl ->
+ cpl.onCarrierPrivilegesChanged(
+ privilegedPackageNames, privilegedUids)));
+ }
+
+ if (mCallback != null) {
+ // AIDL interface does not support Set, keep the List/Array and translate them here
+ Set<String> privilegedPkgNamesSet = Set.copyOf(privilegedPackageNames);
+ Set<Integer> privilegedUidsSet = Arrays.stream(privilegedUids).boxed().collect(
+ Collectors.toSet());
+ Binder.withCleanCallingIdentity(
+ () ->
+ executeSafely(
+ mExecutor,
+ mCallback::get,
+ cpc ->
+ cpc.onCarrierPrivilegesChanged(
+ privilegedPkgNamesSet, privilegedUidsSet)));
+ }
+ }
+
+ @Override
+ public void onCarrierServiceChanged(@Nullable String packageName, int uid) {
+ if (mCallback != null) {
+ Binder.withCleanCallingIdentity(
+ () ->
+ executeSafely(
+ mExecutor,
+ mCallback::get,
+ cpc -> cpc.onCarrierServiceChanged(packageName, uid)));
+ }
}
}
- @GuardedBy("sCarrierPrivilegeListeners")
- private static final WeakHashMap<
- CarrierPrivilegesListener, WeakReference<CarrierPrivilegesListenerWrapper>>
- sCarrierPrivilegeListeners = new WeakHashMap<>();
+ // TODO(b/216549778): Change the map key to CarrierPrivilegesCallback once all clients switch to
+ // CarrierPrivilegesCallback. Before that, the key is either CarrierPrivilegesCallback or
+ // CarrierPrivilegesListener, no logic actually depends on the type.
+ @NonNull
+ @GuardedBy("sCarrierPrivilegeCallbacks")
+ private static final WeakHashMap<Object, WeakReference<CarrierPrivilegesCallbackWrapper>>
+ sCarrierPrivilegeCallbacks = new WeakHashMap<>();
/**
* Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
@@ -1297,7 +1345,11 @@
* @param logicalSlotIndex The SIM slot to listen on
* @param executor The executor where {@code listener} will be invoked
* @param listener The callback to register
+ *
+ * @deprecated Use {@link #addCarrierPrivilegesCallback} instead. This API will be removed
+ * prior to API finalization.
*/
+ @Deprecated
public void addCarrierPrivilegesListener(
int logicalSlotIndex,
@NonNull @CallbackExecutor Executor executor,
@@ -1305,18 +1357,18 @@
if (listener == null || executor == null) {
throw new IllegalArgumentException("listener and executor must be non-null");
}
- synchronized (sCarrierPrivilegeListeners) {
- WeakReference<CarrierPrivilegesListenerWrapper> existing =
- sCarrierPrivilegeListeners.get(listener);
+ synchronized (sCarrierPrivilegeCallbacks) {
+ WeakReference<CarrierPrivilegesCallbackWrapper> existing =
+ sCarrierPrivilegeCallbacks.get(listener);
if (existing != null && existing.get() != null) {
Log.d(TAG, "addCarrierPrivilegesListener: listener already registered");
return;
}
- CarrierPrivilegesListenerWrapper wrapper =
- new CarrierPrivilegesListenerWrapper(listener, executor);
- sCarrierPrivilegeListeners.put(listener, new WeakReference<>(wrapper));
+ CarrierPrivilegesCallbackWrapper wrapper =
+ new CarrierPrivilegesCallbackWrapper(listener, executor);
+ sCarrierPrivilegeCallbacks.put(listener, new WeakReference<>(wrapper));
try {
- sRegistry.addCarrierPrivilegesListener(
+ sRegistry.addCarrierPrivilegesCallback(
logicalSlotIndex,
wrapper,
mContext.getOpPackageName(),
@@ -1331,19 +1383,84 @@
* Unregisters a {@link CarrierPrivilegesListener}.
*
* @param listener The callback to unregister
+ *
+ * @deprecated Use {@link #removeCarrierPrivilegesCallback} instead. The callback will prior
+ * to API finalization.
*/
+ @Deprecated
public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener must be non-null");
}
- synchronized (sCarrierPrivilegeListeners) {
- WeakReference<CarrierPrivilegesListenerWrapper> ref =
- sCarrierPrivilegeListeners.remove(listener);
+ synchronized (sCarrierPrivilegeCallbacks) {
+ WeakReference<CarrierPrivilegesCallbackWrapper> ref =
+ sCarrierPrivilegeCallbacks.remove(listener);
if (ref == null) return;
- CarrierPrivilegesListenerWrapper wrapper = ref.get();
+ CarrierPrivilegesCallbackWrapper wrapper = ref.get();
if (wrapper == null) return;
try {
- sRegistry.removeCarrierPrivilegesListener(wrapper, mContext.getOpPackageName());
+ sRegistry.removeCarrierPrivilegesCallback(wrapper, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Registers a {@link CarrierPrivilegesCallback} on the given {@code logicalSlotIndex} to
+ * receive callbacks when the set of packages with carrier privileges changes. The callback will
+ * immediately be called with the latest state.
+ *
+ * @param logicalSlotIndex The SIM slot to listen on
+ * @param executor The executor where {@code listener} will be invoked
+ * @param callback The callback to register
+ */
+ public void addCarrierPrivilegesCallback(
+ int logicalSlotIndex,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CarrierPrivilegesCallback callback) {
+ if (callback == null || executor == null) {
+ throw new IllegalArgumentException("callback and executor must be non-null");
+ }
+ synchronized (sCarrierPrivilegeCallbacks) {
+ WeakReference<CarrierPrivilegesCallbackWrapper> existing =
+ sCarrierPrivilegeCallbacks.get(callback);
+ if (existing != null && existing.get() != null) {
+ Log.d(TAG, "addCarrierPrivilegesCallback: callback already registered");
+ return;
+ }
+ CarrierPrivilegesCallbackWrapper wrapper =
+ new CarrierPrivilegesCallbackWrapper(callback, executor);
+ sCarrierPrivilegeCallbacks.put(callback, new WeakReference<>(wrapper));
+ try {
+ sRegistry.addCarrierPrivilegesCallback(
+ logicalSlotIndex,
+ wrapper,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters a {@link CarrierPrivilegesCallback}.
+ *
+ * @param callback The callback to unregister
+ */
+ public void removeCarrierPrivilegesCallback(@NonNull CarrierPrivilegesCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("listener must be non-null");
+ }
+ synchronized (sCarrierPrivilegeCallbacks) {
+ WeakReference<CarrierPrivilegesCallbackWrapper> ref =
+ sCarrierPrivilegeCallbacks.remove(callback);
+ if (ref == null) return;
+ CarrierPrivilegesCallbackWrapper wrapper = ref.get();
+ if (wrapper == null) return;
+ try {
+ sRegistry.removeCarrierPrivilegesCallback(wrapper, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1359,15 +1476,33 @@
*/
public void notifyCarrierPrivilegesChanged(
int logicalSlotIndex,
- @NonNull List<String> privilegedPackageNames,
- @NonNull int[] privilegedUids) {
+ @NonNull Set<String> privilegedPackageNames,
+ @NonNull Set<Integer> privilegedUids) {
if (privilegedPackageNames == null || privilegedUids == null) {
throw new IllegalArgumentException(
"privilegedPackageNames and privilegedUids must be non-null");
}
try {
- sRegistry.notifyCarrierPrivilegesChanged(
- logicalSlotIndex, privilegedPackageNames, privilegedUids);
+ // AIDL doesn't support Set yet. Convert Set to List/Array
+ List<String> pkgList = List.copyOf(privilegedPackageNames);
+ int[] uids = privilegedUids.stream().mapToInt(Number::intValue).toArray();
+ sRegistry.notifyCarrierPrivilegesChanged(logicalSlotIndex, pkgList, uids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify listeners that the {@link CarrierService} for current user has changed.
+ *
+ * @param logicalSlotIndex the SIM slot the change occurred on
+ * @param packageName the package name of the changed {@link CarrierService}
+ * @param uid the UID of the changed {@link CarrierService}
+ */
+ public void notifyCarrierServiceChanged(int logicalSlotIndex, @Nullable String packageName,
+ int uid) {
+ try {
+ sRegistry.notifyCarrierServiceChanged(logicalSlotIndex, packageName, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4541f3a..34e7ea7 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,8 +71,8 @@
* Hide back key in the Settings two pane design.
* @hide
*/
- public static final String SETTINGS_HIDE_SECONDARY_PAGE_BACK_BUTTON_IN_TWO_PANE =
- "settings_hide_secondary_page_back_button_in_two_pane";
+ public static final String SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE =
+ "settings_hide_second_layer_page_navigate_up_button_in_two_pane";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -96,10 +96,10 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
- DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
+ DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false");
- DEFAULT_FLAGS.put(SETTINGS_HIDE_SECONDARY_PAGE_BACK_BUTTON_IN_TWO_PANE, "true");
+ DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
}
private static final Set<String> PERSISTENT_FLAGS;
@@ -109,7 +109,7 @@
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
- PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECONDARY_PAGE_BACK_BUTTON_IN_TWO_PANE);
+ PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
}
/**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 71c1b7c..a4841f6 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -23,6 +23,7 @@
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.toInternalType;
import static android.view.InsetsState.toPublicType;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.ime;
@@ -682,9 +683,15 @@
@VisibleForTesting
public boolean onStateChanged(InsetsState state) {
- boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
- false /* excludeInvisibleIme */)
- || !captionInsetsUnchanged();
+ boolean stateChanged = false;
+ if (!CAPTION_ON_SHELL) {
+ stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
+ false /* excludeInvisibleIme */)
+ || captionInsetsUnchanged();
+ } else {
+ stateChanged = !mState.equals(state, false /* excludingCaptionInsets */,
+ false /* excludeInvisibleIme */);
+ }
if (!stateChanged && mLastDispatchedState.equals(state)) {
return false;
}
@@ -758,16 +765,20 @@
}
private boolean captionInsetsUnchanged() {
+ if (CAPTION_ON_SHELL) {
+ return false;
+ }
if (mState.peekSource(ITYPE_CAPTION_BAR) == null
&& mCaptionInsetsHeight == 0) {
- return true;
+ return false;
}
if (mState.peekSource(ITYPE_CAPTION_BAR) != null
&& mCaptionInsetsHeight
== mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) {
- return true;
+ return false;
}
- return false;
+
+ return true;
}
private void startResizingAnimationIfNeeded(InsetsState fromState) {
@@ -1582,11 +1593,15 @@
@Override
public void setCaptionInsetsHeight(int height) {
+ // This method is to be removed once the caption is moved to the shell.
+ if (CAPTION_ON_SHELL) {
+ return;
+ }
if (mCaptionInsetsHeight != height) {
mCaptionInsetsHeight = height;
if (mCaptionInsetsHeight != 0) {
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top,
- mFrame.right, mFrame.top + mCaptionInsetsHeight));
+ mState.getSource(ITYPE_CAPTION_BAR).setFrame(mFrame.left, mFrame.top,
+ mFrame.right, mFrame.top + mCaptionInsetsHeight);
} else {
mState.removeSource(ITYPE_CAPTION_BAR);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bde761e..e79bdce 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -277,6 +277,12 @@
private static final boolean ENABLE_INPUT_LATENCY_TRACKING = true;
/**
+ * Whether the caption is drawn by the shell.
+ * @hide
+ */
+ public static final boolean CAPTION_ON_SHELL = false;
+
+ /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
@@ -867,6 +873,7 @@
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
+ // TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
@@ -1771,6 +1778,7 @@
updateInternalDisplay(displayId, mView.getResources());
mImeFocusController.onMovedToDisplay();
mAttachInfo.mDisplayState = mDisplay.getState();
+ mDisplayInstallOrientation = mDisplay.getInstallOrientation();
// Internal state updated, now notify the view hierarchy.
mView.dispatchMovedToDisplay(mDisplay, config);
}
@@ -2561,6 +2569,9 @@
}
private boolean updateCaptionInsets() {
+ if (CAPTION_ON_SHELL) {
+ return false;
+ }
if (!(mView instanceof DecorView)) return false;
final int captionInsetsHeight = ((DecorView) mView).getCaptionInsetsHeight();
final Rect captionFrame = new Rect();
@@ -3439,6 +3450,12 @@
mReportNextDraw = false;
pendingDrawFinished();
}
+
+ // Make sure the consumer is not waiting if the view root was just made invisible.
+ if (mBLASTDrawConsumer != null) {
+ mBLASTDrawConsumer.accept(null);
+ mBLASTDrawConsumer = null;
+ }
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 3a7a544..7f8a68d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4595,7 +4595,7 @@
if (includeEditorBounds) {
final RectF bounds = new RectF();
- mTextView.getBoundsOnScreen(bounds, false /* clipToParent */);
+ bounds.set(0 /* left */, 0 /* top */, mTextView.getWidth(), mTextView.getHeight());
EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
//TODO(b/210039666): add Handwriting bounds once they're available.
builder.setEditorBoundsInfo(
diff --git a/core/java/com/android/internal/graphics/SfVsyncFrameCallbackProvider.java b/core/java/com/android/internal/graphics/SfVsyncFrameCallbackProvider.java
index 45555bf..dbbe4b9 100644
--- a/core/java/com/android/internal/graphics/SfVsyncFrameCallbackProvider.java
+++ b/core/java/com/android/internal/graphics/SfVsyncFrameCallbackProvider.java
@@ -24,6 +24,7 @@
*
* @hide
*/
+// TODO(b/222698397): remove getSfInstance/this class usage and use vsyncId for transactions
public final class SfVsyncFrameCallbackProvider implements AnimationFrameCallbackProvider {
private final Choreographer mChoreographer;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 3746bfd..5947e66 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -62,6 +62,10 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
@@ -176,6 +180,10 @@
public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
public static final int CUJ_UNFOLD_ANIM = 44;
+ public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
+ public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
+ public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
+ public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
private static final int NO_STATSD_LOGGING = -1;
@@ -229,6 +237,10 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS,
};
private static volatile InteractionJankMonitor sInstance;
@@ -294,6 +306,10 @@
CUJ_ONE_HANDED_ENTER_TRANSITION,
CUJ_ONE_HANDED_EXIT_TRANSITION,
CUJ_UNFOLD_ANIM,
+ CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
+ CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
+ CUJ_SUW_LOADING_TO_NEXT_FLOW,
+ CUJ_SUW_LOADING_SCREEN_FOR_STATUS
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -701,6 +717,14 @@
return "ONE_HANDED_EXIT_TRANSITION";
case CUJ_UNFOLD_ANIM:
return "UNFOLD_ANIM";
+ case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
+ return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
+ case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
+ return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
+ case CUJ_SUW_LOADING_TO_NEXT_FLOW:
+ return "SUW_LOADING_TO_NEXT_FLOW";
+ case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
+ return "SUW_LOADING_SCREEN_FOR_STATUS";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/AudioPowerCalculator.java b/core/java/com/android/internal/os/AudioPowerCalculator.java
index f9310b0..ebf0ca2 100644
--- a/core/java/com/android/internal/os/AudioPowerCalculator.java
+++ b/core/java/com/android/internal/os/AudioPowerCalculator.java
@@ -78,7 +78,9 @@
final double powerMah = mPowerEstimator.calculatePower(durationMs);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, powerMah);
- total.durationMs += durationMs;
- total.powerMah += powerMah;
+ if (!app.isVirtualUid()) {
+ total.durationMs += durationMs;
+ total.powerMah += powerMah;
+ }
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 70b9639..5253956 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -546,9 +546,9 @@
final LongArrayMultiStateCounter onBatteryScreenOffCounter =
u.getProcStateScreenOffTimeCounter().getCounter();
- if (uid == parentUid) {
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter,
+ if (uid == parentUid || Process.isSdkSandboxUid(uid)) {
+ mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter, timestampMs);
+ mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryScreenOffCounter,
timestampMs);
} else {
Uid.ChildUid childUid = u.getChildUid(uid);
@@ -4560,7 +4560,10 @@
mIsolatedUidRefCounts.put(uid, refCount + 1);
}
- public int mapUid(int uid) {
+ private int mapUid(int uid) {
+ if (Process.isSdkSandboxUid(uid)) {
+ return Process.getAppUidForSdkSandboxUid(uid);
+ }
int isolated = mIsolatedUids.get(uid, -1);
return isolated > 0 ? isolated : uid;
}
@@ -4656,16 +4659,18 @@
long elapsedRealtimeMs, long uptimeMs) {
int parentUid = mapUid(uid);
if (uid != parentUid) {
- // Isolated UIDs process state is already rolled up into parent, so no need to track
- // Otherwise the parent's process state will get downgraded incorrectly
- return;
+ if (Process.isIsolated(uid)) {
+ // Isolated UIDs process state is already rolled up into parent, so no need to track
+ // Otherwise the parent's process state will get downgraded incorrectly
+ return;
+ }
}
// TODO(b/155216561): It is possible for isolated uids to be in a higher
// state than its parent uid. We should track the highest state within the union of host
// and isolated uids rather than only the parent uid.
FrameworkStatsLog.write(FrameworkStatsLog.UID_PROCESS_STATE_CHANGED, uid,
ActivityManager.processStateAmToProto(state));
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(parentUid, elapsedRealtimeMs, uptimeMs)
.updateUidProcessStateLocked(state, elapsedRealtimeMs, uptimeMs);
}
@@ -15970,6 +15975,9 @@
public Uid getUidStatsLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
Uid u = mUidStats.get(uid);
if (u == null) {
+ if (Process.isSdkSandboxUid(uid)) {
+ Log.wtf(TAG, "Tracking an SDK Sandbox UID");
+ }
u = new Uid(this, uid, elapsedRealtimeMs, uptimeMs);
mUidStats.put(uid, u);
}
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index a1c1917..81c6ee7 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -22,6 +22,7 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Parcel;
+import android.os.Process;
import android.os.SystemClock;
import android.os.UidBatteryConsumer;
import android.util.Log;
@@ -162,6 +163,8 @@
final boolean includeProcessStateData = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
&& mStats.isProcessStateDataAvailable();
+ final boolean includeVirtualUids = ((query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
mStats.getCustomEnergyConsumerNames(), includePowerModels,
@@ -174,6 +177,10 @@
SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats();
for (int i = uidStats.size() - 1; i >= 0; i--) {
final BatteryStats.Uid uid = uidStats.valueAt(i);
+ if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) {
+ continue;
+ }
+
batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid)
.setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND,
getProcessBackgroundTimeMs(uid, realtimeUs))
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 2ebf689..e52c8a3 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -139,8 +139,10 @@
BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.powerMah,
powerModel);
- powerAndDuration.totalDurationMs += powerAndDuration.durationMs;
- powerAndDuration.totalPowerMah += powerAndDuration.powerMah;
+ if (!app.isVirtualUid()) {
+ powerAndDuration.totalDurationMs += powerAndDuration.durationMs;
+ powerAndDuration.totalPowerMah += powerAndDuration.powerMah;
+ }
if (query.isProcessStateDataNeeded() && powerAndDuration.keys != null) {
for (int j = 0; j < powerAndDuration.keys.length; j++) {
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 1fc2baf..8704e93 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -117,7 +117,9 @@
}
}
calculateApp(app, app.getBatteryStatsUid(), query, result, keys);
- totalPowerMah += result.powerMah;
+ if (!app.isVirtualUid()) {
+ totalPowerMah += result.powerMah;
+ }
}
final long consumptionUC = batteryStats.getCpuMeasuredBatteryConsumptionUC();
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index cbbb526..0853bd8 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -96,7 +96,9 @@
app.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
customMeasuredPowerMah[i]);
- newTotalPowerMah[i] += customMeasuredPowerMah[i];
+ if (!app.isVirtualUid()) {
+ newTotalPowerMah[i] += customMeasuredPowerMah[i];
+ }
}
}
return newTotalPowerMah;
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index 0f78306..070783a 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -58,8 +58,11 @@
final long consumptionUC =
app.getBatteryStatsUid().getGnssMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
- appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), powerModel,
+ final double powerMah = calculateApp(app, app.getBatteryStatsUid(), powerModel,
rawRealtimeUs, averageGnssPowerMa, consumptionUC);
+ if (!app.isVirtualUid()) {
+ appsPowerMah += powerMah;
+ }
}
final long consumptionUC = batteryStats.getGnssMeasuredBatteryConsumptionUC();
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index f4624de..d0df45c 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -136,12 +136,14 @@
PowerAndDuration total,
BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
- total.totalAppDurationMs += radioActiveDurationMs;
-
final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
- total.totalAppPowerMah += powerMah;
+
+ if (!app.isVirtualUid()) {
+ total.totalAppDurationMs += radioActiveDurationMs;
+ total.totalAppPowerMah += powerMah;
+ }
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
radioActiveDurationMs)
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 67d3d6e..5ca1a85 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -96,8 +96,10 @@
appPowerAndDuration.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
appPowerAndDuration.powerMah, powerModel);
- totalAppPower += appPowerAndDuration.powerMah;
- totalAppDuration += appPowerAndDuration.durationMs;
+ if (!app.isVirtualUid()) {
+ totalAppPower += appPowerAndDuration.powerMah;
+ totalAppDuration += appPowerAndDuration.durationMs;
+ }
}
break;
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
@@ -192,10 +194,13 @@
long totalActivityTimeMs = 0;
final SparseLongArray activityTimeArray = new SparseLongArray();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
- final BatteryStats.Uid uid = uidBatteryConsumerBuilders.valueAt(i).getBatteryStatsUid();
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ final BatteryStats.Uid uid = app.getBatteryStatsUid();
final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
activityTimeArray.put(uid.getUid(), timeMs);
- totalActivityTimeMs += timeMs;
+ if (!app.isVirtualUid()) {
+ totalActivityTimeMs += timeMs;
+ }
}
if (totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
index 4a9c91d..573692e 100644
--- a/core/java/com/android/internal/os/SensorPowerCalculator.java
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -51,7 +51,9 @@
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
- appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), rawRealtimeUs);
+ if (!app.isVirtualUid()) {
+ appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), rawRealtimeUs);
+ }
}
builder.getAggregateBatteryConsumerBuilder(
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index 22cff6e..79e3a19 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -49,7 +49,11 @@
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
- UidBatteryConsumer.Builder uidBuilder = uidBatteryConsumerBuilders.valueAt(i);
+ final UidBatteryConsumer.Builder uidBuilder = uidBatteryConsumerBuilders.valueAt(i);
+ if (uidBuilder.isVirtualUid()) {
+ continue;
+ }
+
final int uid = uidBuilder.getUid();
if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
continue;
diff --git a/core/java/com/android/internal/os/VideoPowerCalculator.java b/core/java/com/android/internal/os/VideoPowerCalculator.java
index a222bcb..2daf15e 100644
--- a/core/java/com/android/internal/os/VideoPowerCalculator.java
+++ b/core/java/com/android/internal/os/VideoPowerCalculator.java
@@ -75,7 +75,9 @@
final double powerMah = mPowerEstimator.calculatePower(durationMs);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, powerMah);
- total.durationMs += durationMs;
- total.powerMah += powerMah;
+ if (!app.isVirtualUid()) {
+ total.durationMs += durationMs;
+ total.powerMah += powerMah;
+ }
}
}
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index 0251e1c..3ae7113 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -62,8 +62,10 @@
BatteryStats.STATS_SINCE_CHARGED);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
- totalAppDurationMs += result.durationMs;
- appPowerMah += result.powerMah;
+ if (!app.isVirtualUid()) {
+ totalAppDurationMs += result.durationMs;
+ appPowerMah += result.powerMah;
+ }
if (app.getUid() == Process.ROOT_UID) {
osBatteryConsumer = app;
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 8c3fb86..2181821 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -111,9 +111,10 @@
calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), powerModel,
rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED,
batteryStats.hasWifiActivityReporting(), consumptionUC);
-
- totalAppDurationMs += powerDurationAndTraffic.durationMs;
- totalAppPowerMah += powerDurationAndTraffic.powerMah;
+ if (!app.isVirtualUid()) {
+ totalAppDurationMs += powerDurationAndTraffic.durationMs;
+ totalAppPowerMah += powerDurationAndTraffic.powerMah;
+ }
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
powerDurationAndTraffic.durationMs);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 40e4085..89ac722 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -30,6 +30,7 @@
import static android.view.View.MeasureSpec.getMode;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -2120,7 +2121,9 @@
* corresponding insets change to the InsetsController.
*/
public void notifyCaptionHeightChanged() {
- getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight());
+ if (!CAPTION_ON_SHELL) {
+ getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight());
+ }
}
void setWindow(PhoneWindow phoneWindow) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d629d66..089179d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -297,6 +297,11 @@
*/
void runGcForTest();
+ /**
+ * Send a request to SystemUI to put a given active tile in listening state
+ */
+ void requestTileServiceListeningState(in ComponentName componentName);
+
void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
void cancelRequestAddTile(in String packageName);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 1d60c50..2ee5e79 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -91,7 +91,7 @@
void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed);
void hideCurrentInputMethodForBubbles();
void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
- void clearInlineReplyUriPermissions(String key);
+ oneway void clearInlineReplyUriPermissions(String key);
void onNotificationFeedbackReceived(String key, in Bundle feedback);
void onGlobalActionsShown();
@@ -171,6 +171,11 @@
*/
void suppressAmbientDisplay(boolean suppress);
+ /**
+ * Send a request to SystemUI to put a given active tile in listening state
+ */
+ void requestTileServiceListeningState(in ComponentName componentName, int userId);
+
void requestAddTile(in ComponentName componentName, in CharSequence label, in Icon icon, int userId, in IAddTileResultCallback callback);
void cancelRequestAddTile(in String packageName);
diff --git a/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl b/core/java/com/android/internal/telephony/ICarrierPrivilegesCallback.aidl
similarity index 84%
rename from core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
rename to core/java/com/android/internal/telephony/ICarrierPrivilegesCallback.aidl
index 6ca8cec..0c8e73f 100644
--- a/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
+++ b/core/java/com/android/internal/telephony/ICarrierPrivilegesCallback.aidl
@@ -16,7 +16,8 @@
package com.android.internal.telephony;
-oneway interface ICarrierPrivilegesListener {
+oneway interface ICarrierPrivilegesCallback {
void onCarrierPrivilegesChanged(
in List<String> privilegedPackageNames, in int[] privilegedUids);
+ void onCarrierServiceChanged(in String carrierServicePackageName, in int carrierServiceUid);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 9712d7e..c7fa757 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,7 +32,7 @@
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
-import com.android.internal.telephony.ICarrierPrivilegesListener;
+import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -102,9 +102,11 @@
void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
in List<LinkCapacityEstimate> linkCapacityEstimateList);
- void addCarrierPrivilegesListener(
- int phoneId, ICarrierPrivilegesListener callback, String pkg, String featureId);
- void removeCarrierPrivilegesListener(ICarrierPrivilegesListener callback, String pkg);
+ void addCarrierPrivilegesCallback(
+ int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId);
+ void removeCarrierPrivilegesCallback(ICarrierPrivilegesCallback callback, String pkg);
void notifyCarrierPrivilegesChanged(
int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
+ void notifyCarrierServiceChanged(int phoneId, in String packageName, int uid);
+
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index db4bc2c..851e8e0 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -97,7 +97,6 @@
boolean hasSecureLockScreen();
boolean tryUnlockWithCachedUnifiedChallenge(int userId);
void removeCachedUnifiedChallenge(int userId);
- void updateEncryptionPassword(int type, in byte[] password);
boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 1e11c6d..521b2f6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -722,38 +722,14 @@
return true;
}
- private void updateCryptoUserInfo(int userId) {
- if (userId != UserHandle.USER_SYSTEM) {
- return;
- }
-
- final String ownerInfo = isOwnerInfoEnabled(userId) ? getOwnerInfo(userId) : "";
-
- IBinder service = ServiceManager.getService("mount");
- if (service == null) {
- Log.e(TAG, "Could not find the mount service to update the user info");
- return;
- }
-
- IStorageManager storageManager = IStorageManager.Stub.asInterface(service);
- try {
- Log.d(TAG, "Setting owner info");
- storageManager.setField(StorageManager.OWNER_INFO_KEY, ownerInfo);
- } catch (RemoteException e) {
- Log.e(TAG, "Error changing user info", e);
- }
- }
-
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setOwnerInfo(String info, int userId) {
setString(LOCK_SCREEN_OWNER_INFO, info, userId);
- updateCryptoUserInfo(userId);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setOwnerInfoEnabled(boolean enabled, int userId) {
setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled, userId);
- updateCryptoUserInfo(userId);
}
@UnsupportedAppUsage
@@ -808,17 +784,6 @@
}
/**
- * Clears the encryption password.
- */
- public void clearEncryptionPassword() {
- try {
- getLockSettings().updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't clear encryption password");
- }
- }
-
- /**
* Retrieves the quality mode for {@code userHandle}.
* @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
*
@@ -843,7 +808,7 @@
*/
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
LockscreenCredential profilePassword) {
- if (!isCredentialSharedWithParent(userHandle)) {
+ if (!isCredentialSharableWithParent(userHandle)) {
return;
}
try {
@@ -859,7 +824,7 @@
* Returns true if {@code userHandle} is a managed profile with separate challenge.
*/
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
- return isCredentialSharedWithParent(userHandle) && hasSeparateChallenge(userHandle);
+ return isCredentialSharableWithParent(userHandle) && hasSeparateChallenge(userHandle);
}
/**
@@ -884,8 +849,8 @@
return info != null && info.isManagedProfile();
}
- private boolean isCredentialSharedWithParent(int userHandle) {
- return getUserManager(userHandle).isCredentialSharedWithParent();
+ private boolean isCredentialSharableWithParent(int userHandle) {
+ return getUserManager(userHandle).isCredentialSharableWithParent();
}
/**
@@ -1042,24 +1007,6 @@
*/
public void setVisiblePatternEnabled(boolean enabled, int userId) {
setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled, userId);
-
- // Update for crypto if owner
- if (userId != UserHandle.USER_SYSTEM) {
- return;
- }
-
- IBinder service = ServiceManager.getService("mount");
- if (service == null) {
- Log.e(TAG, "Could not find the mount service to update the user info");
- return;
- }
-
- IStorageManager storageManager = IStorageManager.Stub.asInterface(service);
- try {
- storageManager.setField(StorageManager.PATTERN_VISIBLE_KEY, enabled ? "1" : "0");
- } catch (RemoteException e) {
- Log.e(TAG, "Error changing pattern visible state", e);
- }
}
public boolean isVisiblePatternEverChosen(int userId) {
@@ -1070,23 +1017,7 @@
* Set whether the visible password is enabled for cryptkeeper screen.
*/
public void setVisiblePasswordEnabled(boolean enabled, int userId) {
- // Update for crypto if owner
- if (userId != UserHandle.USER_SYSTEM) {
- return;
- }
-
- IBinder service = ServiceManager.getService("mount");
- if (service == null) {
- Log.e(TAG, "Could not find the mount service to update the user info");
- return;
- }
-
- IStorageManager storageManager = IStorageManager.Stub.asInterface(service);
- try {
- storageManager.setField(StorageManager.PASSWORD_VISIBLE_KEY, enabled ? "1" : "0");
- } catch (RemoteException e) {
- Log.e(TAG, "Error changing password visible state", e);
- }
+ // No longer does anything.
}
/**
@@ -1204,7 +1135,7 @@
public List<ComponentName> getEnabledTrustAgents(int userId) {
String serialized = getString(ENABLED_TRUST_AGENTS, userId);
if (TextUtils.isEmpty(serialized)) {
- return null;
+ return new ArrayList<ComponentName>();
}
String[] split = serialized.split(",");
ArrayList<ComponentName> activeTrustAgents = new ArrayList<ComponentName>(split.length);
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 2b6b933..01cec77 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -45,6 +45,7 @@
import android.util.IntArray;
import android.util.Log;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.RenderNodeAnimator;
@@ -82,10 +83,12 @@
private static final int DOT_ACTIVATION_DURATION_MILLIS = 50;
private static final int DOT_RADIUS_INCREASE_DURATION_MILLIS = 96;
private static final int DOT_RADIUS_DECREASE_DURATION_MILLIS = 192;
+ private static final float MIN_DOT_HIT_FACTOR = 0.2f;
private final CellState[][] mCellStates;
private final int mDotSize;
private final int mDotSizeActivated;
+ private final float mDotHitFactor;
private final int mPathWidth;
private boolean mDrawingProfilingStarted = false;
@@ -143,12 +146,11 @@
private boolean mPatternInProgress = false;
private boolean mFadePattern = true;
- private float mHitFactor = 0.6f;
-
@UnsupportedAppUsage
private float mSquareWidth;
@UnsupportedAppUsage
private float mSquareHeight;
+ private float mDotHitRadius;
private final LinearGradient mFadeOutGradientShader;
private final Path mCurrentPath = new Path();
@@ -164,8 +166,7 @@
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mLinearOutSlowInInterpolator;
- private PatternExploreByTouchHelper mExploreByTouchHelper;
- private AudioManager mAudioManager;
+ private final PatternExploreByTouchHelper mExploreByTouchHelper;
private Drawable mSelectedDrawable;
private Drawable mNotSelectedDrawable;
@@ -349,6 +350,9 @@
mDotSize = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_size);
mDotSizeActivated = getResources().getDimensionPixelSize(
R.dimen.lock_pattern_dot_size_activated);
+ TypedValue outValue = new TypedValue();
+ getResources().getValue(R.dimen.lock_pattern_dot_hit_factor, outValue, true);
+ mDotHitFactor = Math.max(Math.min(outValue.getFloat(), 1f), MIN_DOT_HIT_FACTOR);
mUseLockPatternDrawable = getResources().getBoolean(R.bool.use_lock_pattern_drawable);
if (mUseLockPatternDrawable) {
@@ -375,7 +379,6 @@
AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
mExploreByTouchHelper = new PatternExploreByTouchHelper(this);
setAccessibilityDelegate(mExploreByTouchHelper);
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int fadeAwayGradientWidth = getResources().getDimensionPixelSize(
R.dimen.lock_pattern_fade_away_gradient_width);
@@ -679,6 +682,7 @@
final int height = h - mPaddingTop - mPaddingBottom;
mSquareHeight = height / 3.0f;
mExploreByTouchHelper.invalidateRoot();
+ mDotHitRadius = Math.min(mSquareHeight / 2, mSquareWidth / 2) * mDotHitFactor;
if (mUseLockPatternDrawable) {
mNotSelectedDrawable.setBounds(mPaddingLeft, mPaddingTop, width, height);
@@ -890,63 +894,30 @@
return set;
}
- // helper method to find which cell a point maps to
+ @Nullable
private Cell checkForNewHit(float x, float y) {
-
- final int rowHit = getRowHit(y);
- if (rowHit < 0) {
- return null;
+ Cell cellHit = detectCellHit(x, y);
+ if (cellHit != null && !mPatternDrawLookup[cellHit.row][cellHit.column]) {
+ return cellHit;
}
- final int columnHit = getColumnHit(x);
- if (columnHit < 0) {
- return null;
- }
-
- if (mPatternDrawLookup[rowHit][columnHit]) {
- return null;
- }
- return Cell.of(rowHit, columnHit);
+ return null;
}
- /**
- * Helper method to find the row that y falls into.
- * @param y The y coordinate
- * @return The row that y falls in, or -1 if it falls in no row.
- */
- private int getRowHit(float y) {
-
- final float squareHeight = mSquareHeight;
- float hitSize = squareHeight * mHitFactor;
-
- float offset = mPaddingTop + (squareHeight - hitSize) / 2f;
- for (int i = 0; i < 3; i++) {
-
- final float hitTop = offset + squareHeight * i;
- if (y >= hitTop && y <= hitTop + hitSize) {
- return i;
+ /** Helper method to find which cell a point maps to. */
+ @Nullable
+ private Cell detectCellHit(float x, float y) {
+ final float hitRadiusSquared = mDotHitRadius * mDotHitRadius;
+ for (int row = 0; row < 3; row++) {
+ for (int column = 0; column < 3; column++) {
+ float centerY = getCenterYForRow(row);
+ float centerX = getCenterXForColumn(column);
+ if ((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)
+ < hitRadiusSquared) {
+ return Cell.of(row, column);
+ }
}
}
- return -1;
- }
-
- /**
- * Helper method to find the column x fallis into.
- * @param x The x coordinate.
- * @return The column that x falls in, or -1 if it falls in no column.
- */
- private int getColumnHit(float x) {
- final float squareWidth = mSquareWidth;
- float hitSize = squareWidth * mHitFactor;
-
- float offset = mPaddingLeft + (squareWidth - hitSize) / 2f;
- for (int i = 0; i < 3; i++) {
-
- final float hitLeft = offset + squareWidth * i;
- if (x >= hitLeft && x <= hitLeft + hitSize) {
- return i;
- }
- }
- return -1;
+ return null;
}
@Override
@@ -1553,8 +1524,7 @@
protected int getVirtualViewAt(float x, float y) {
// This must use the same hit logic for the screen to ensure consistency whether
// accessibility is on or off.
- int id = getVirtualViewIdForHit(x, y);
- return id;
+ return getVirtualViewIdForHit(x, y);
}
@Override
@@ -1670,12 +1640,11 @@
final int col = ordinal % 3;
float centerX = getCenterXForColumn(col);
float centerY = getCenterYForRow(row);
- float cellheight = mSquareHeight * mHitFactor * 0.5f;
- float cellwidth = mSquareWidth * mHitFactor * 0.5f;
- bounds.left = (int) (centerX - cellwidth);
- bounds.right = (int) (centerX + cellwidth);
- bounds.top = (int) (centerY - cellheight);
- bounds.bottom = (int) (centerY + cellheight);
+ float cellHitRadius = mDotHitRadius;
+ bounds.left = (int) (centerX - cellHitRadius);
+ bounds.right = (int) (centerX + cellHitRadius);
+ bounds.top = (int) (centerY - cellHitRadius);
+ bounds.bottom = (int) (centerY + cellHitRadius);
return bounds;
}
@@ -1694,16 +1663,12 @@
* @return VIRTUAL_BASE_VIEW_ID+id or 0 if no view was hit
*/
private int getVirtualViewIdForHit(float x, float y) {
- final int rowHit = getRowHit(y);
- if (rowHit < 0) {
+ Cell cellHit = detectCellHit(x, y);
+ if (cellHit == null) {
return ExploreByTouchHelper.INVALID_ID;
}
- final int columnHit = getColumnHit(x);
- if (columnHit < 0) {
- return ExploreByTouchHelper.INVALID_ID;
- }
- boolean dotAvailable = mPatternDrawLookup[rowHit][columnHit];
- int dotId = (rowHit * 3 + columnHit) + VIRTUAL_BASE_VIEW_ID;
+ boolean dotAvailable = mPatternDrawLookup[cellHit.row][cellHit.column];
+ int dotId = (cellHit.row * 3 + cellHit.column) + VIRTUAL_BASE_VIEW_ID;
int view = dotAvailable ? dotId : ExploreByTouchHelper.INVALID_ID;
if (DEBUG_A11Y) Log.v(TAG, "getVirtualViewIdForHit(" + x + "," + y + ") => "
+ view + "avail =" + dotAvailable);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index eedf7fa..eba6cca 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1567,6 +1567,7 @@
REG_JNI(register_android_graphics_classes),
REG_JNI(register_android_graphics_BLASTBufferQueue),
REG_JNI(register_android_graphics_GraphicBuffer),
+ REG_JNI(register_android_graphics_GraphicsStatsService),
REG_JNI(register_android_graphics_SurfaceTexture),
REG_JNI(register_android_database_CursorWindow),
REG_JNI(register_android_database_SQLiteConnection),
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5b7092c..7bc6905 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -895,7 +895,7 @@
// pthread_setname_np fails rather than truncating long strings.
char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
- strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
+ strlcpy(buf, name_start_ptr, sizeof(buf));
errno = pthread_setname_np(pthread_self(), buf);
if (errno != 0) {
ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 0c05da5..679a4f0 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -34,6 +34,7 @@
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/system_properties.h>
#include <vector>
namespace android {
@@ -43,10 +44,10 @@
using android::zygote::ZygoteFailure;
// WARNING: Knows a little about the wire protocol used to communicate with Zygote.
-// TODO: Fix error handling.
+// Commands and nice names have large arbitrary size limits to avoid dynamic memory allocation.
constexpr size_t MAX_COMMAND_BYTES = 32768;
-constexpr size_t NICE_NAME_BYTES = 50;
+constexpr size_t NICE_NAME_BYTES = 128;
// A buffer optionally bundled with a file descriptor from which we can fill it.
// Does not own the file descriptor; destroying a NativeCommandBuffer does not
@@ -190,6 +191,9 @@
size_t copy_len = std::min(name_len, NICE_NAME_BYTES - 1);
memcpy(mNiceName, arg_start + NN_LENGTH, copy_len);
mNiceName[copy_len] = '\0';
+ if (haveWrapProperty()) {
+ return false;
+ }
continue;
}
if (arg_end - arg_start == IW_LENGTH
@@ -222,6 +226,8 @@
}
saw_setgid = true;
}
+ // ro.debuggable can be handled entirely in the child unless --invoke-with is also specified.
+ // Thus we do not need to check it here.
}
return saw_runtime_args && saw_setuid && saw_setgid;
}
@@ -249,6 +255,14 @@
}
private:
+ bool haveWrapProperty() {
+ static const char* WRAP = "wrap.";
+ static const size_t WRAP_LENGTH = strlen(WRAP);
+ char propNameBuf[WRAP_LENGTH + NICE_NAME_BYTES];
+ strcpy(propNameBuf, WRAP);
+ strlcpy(propNameBuf + WRAP_LENGTH, mNiceName, NICE_NAME_BYTES);
+ return __system_property_find(propNameBuf) != nullptr;
+ }
// Picky version of atoi(). No sign or unexpected characters allowed. Return -1 on failure.
static int digitsVal(char* start, char* end) {
int result = 0;
@@ -269,7 +283,7 @@
uint32_t mNext; // Index of first character past last line returned by readLine.
int32_t mLinesLeft; // Lines in current command that haven't yet been read.
int mFd; // Open file descriptor from which we can read more. -1 if none.
- char mNiceName[NICE_NAME_BYTES];
+ char mNiceName[NICE_NAME_BYTES]; // Always null terminated.
char mBuffer[MAX_COMMAND_BYTES];
};
@@ -372,6 +386,7 @@
jint minUid,
jstring managed_nice_name) {
+ ALOGI("Entering forkRepeatedly native zygote loop");
NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
int session_socket = n_buffer->getFd();
std::vector<int> session_socket_fds {session_socket};
@@ -400,7 +415,8 @@
socklen_t cred_size = sizeof credentials;
if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
|| cred_size != sizeof credentials) {
- fail_fn_1(CREATE_ERROR("ForkMany failed to get initial credentials, %s", strerror(errno)));
+ fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
+ strerror(errno)));
}
bool first_time = true;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 21baa0b..ddc37ee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1030,10 +1030,10 @@
targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
<p>Protection level: dangerous -->
- <permission android:name="android.permission.READ_MEDIA_IMAGE"
+ <permission android:name="android.permission.READ_MEDIA_IMAGES"
android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_readMediaImage"
- android:description="@string/permdesc_readMediaImage"
+ android:label="@string/permlab_readMediaImages"
+ android:description="@string/permdesc_readMediaImages"
android:protectionLevel="dangerous" />
<!-- Allows an application to write to external storage.
@@ -1901,11 +1901,21 @@
<!-- Allows applications to enable/disable wifi auto join. This permission
is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs
to improve wifi performance.
- <p>Not for use by third-party applications. -->
+ <p>Not for use by third-party applications.
+ @deprecated will be replaced with MANAGE_WIFI_NETWORK_SELECTION -->
<permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN"
android:protectionLevel="signature|privileged|knownSigner"
android:knownCerts="@array/wifi_known_signers" />
+ <!-- This permission is used to let OEMs grant their trusted app access to a subset of
+ privileged wifi APIs to improve wifi performance. Allows applications to manage
+ Wi-Fi network selection related features such as enable or disable global auto-join,
+ modify connectivity scan intervals, and approve Wi-Fi Direct connections.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_NETWORK_SELECTION"
+ android:protectionLevel="signature|privileged|knownSigner"
+ android:knownCerts="@array/wifi_known_signers" />
+
<!-- Allows applications to get notified when a Wi-Fi interface request cannot
be satisfied without tearing down one or more other interfaces, and provide a decision
whether to approve the request or reject it.
@@ -3089,7 +3099,7 @@
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
- android:protectionLevel="signature|recents|role"/>
+ android:protectionLevel="signature|recents|role|installer"/>
<!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
@hide
@@ -6150,10 +6160,10 @@
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
android:protectionLevel="signature|recents" />
- <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
- window to the window where the touch currently is on top of. @hide -->
+ <!-- @SystemApi Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the
+ current window to the window where the touch currently is on top of. @hide -->
<permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
- android:protectionLevel="signature|recents" />
+ android:protectionLevel="signature|privileged|recents|role" />
<!-- Allows the caller to change the associations between input devices and displays.
Very dangerous! @hide -->
<permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
@@ -6829,6 +6839,13 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+ </intent-filter>
+ </receiver>
+
<service android:name="android.hardware.location.GeofenceHardwareService"
android:permission="android.permission.LOCATION_HARDWARE"
android:exported="false" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0bdf716..269aa1b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2335,7 +2335,7 @@
<!-- Remote server that can provide NTP responses. -->
<string translatable="false" name="config_ntpServer">time.android.com</string>
<!-- Normal polling frequency in milliseconds -->
- <integer name="config_ntpPollingInterval">86400000</integer>
+ <integer name="config_ntpPollingInterval">64800000</integer>
<!-- Try-again polling interval in milliseconds, in case the network request failed -->
<integer name="config_ntpPollingIntervalShorter">60000</integer>
<!-- Number of times to try again with the shorter interval, before backing
@@ -2723,7 +2723,7 @@
<string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
<!-- Whether force to enable telephony new data stack or not -->
- <bool name="config_force_enable_telephony_new_data_stack">true</bool>
+ <bool name="config_force_enable_telephony_new_data_stack">false</bool>
<!-- Whether WiFi display is supported by this device.
There are many prerequisites for this feature to work correctly.
@@ -2988,6 +2988,12 @@
</string-array>
+ <!-- When migrating notification settings into the permission framework, whether all existing
+ apps should be marked as 'user-set' (true) or whether only the apps that have explicitly
+ modified notification settings should be marked as 'user-set' (false). Users will not see
+ system generated permission prompts for 'user-set' apps. -->
+ <bool name="config_notificationForceUserSetOnUpgrade">true</bool>
+
<!-- Default Gravity setting for the system Toast view. Equivalent to: Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM -->
<integer name="config_toastDefaultGravity">0x00000051</integer>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 1b9f7fe..44c5512 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -668,6 +668,9 @@
<dimen name="lock_pattern_dot_line_width">22dp</dimen>
<dimen name="lock_pattern_dot_size">14dp</dimen>
<dimen name="lock_pattern_dot_size_activated">30dp</dimen>
+ <!-- How much of the cell space is classified as hit areas [0..1] where 1 means that hit area is
+ a circle with diameter equals to cell minimum side min(width, height). -->
+ <item type="dimen" format="float" name="lock_pattern_dot_hit_factor">0.6</item>
<!-- Width of a gradient applied to a lock pattern line while its disappearing animation. -->
<dimen name="lock_pattern_fade_away_gradient_width">8dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 602e42d..04a70cb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1248,14 +1248,13 @@
Malicious apps may use this to erase or modify your call log.</string>
<!-- Title of the body sensors permission, listed so the user can decide whether to allow the application to access body sensor data. [CHAR LIMIT=80] -->
- <string name="permlab_bodySensors">access body sensors (like heart rate monitors)
- </string>
+ <string name="permlab_bodySensors">Access body sensor data, like heart rate, while in use</string>
<!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
- <string name="permdesc_bodySensors" product="default">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc.</string>
+ <string name="permdesc_bodySensors" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in use.</string>
<!-- Title of the background body sensors permission, listed so the user can decide whether to allow the application to access body sensor data in the background. [CHAR LIMIT=80] -->
- <string name="permlab_bodySensors_background">access body sensors (like heart rate monitors) while in the background</string>
+ <string name="permlab_bodySensors_background">Access body sensor data, like heart rate, while in the background</string>
<!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
- <string name="permdesc_bodySensors_background" product="default">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc. while in the background.</string>
+ <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCalendar">Read calendar events and details</string>
@@ -1919,9 +1918,9 @@
<string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permlab_readMediaImage">read image files from shared storage</string>
+ <string name="permlab_readMediaImages">read image files from shared storage</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
+ <string name="permdesc_readMediaImages">Allows the app to read image files from your shared storage.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
<string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
@@ -2415,7 +2414,7 @@
<!-- On the unlock pattern screen, shown at the top of the unlock screen to tell the user what to do. Below this text is the place for theu ser to draw the pattern. -->
<string name="lockscreen_pattern_instructions">Draw pattern to unlock</string>
<!-- Button at the bottom of the unlock screen to make an emergency call or access other emergency assistance functions. -->
- <string name="lockscreen_emergency_call">Emergency call</string>
+ <string name="lockscreen_emergency_call">Emergency</string>
<!-- Button at the bottom of the unlock screen that lets the user return to a call -->
<string name="lockscreen_return_to_call">Return to call</string>
<!-- Shown to confirm that the user entered their lock pattern correctly. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8f3abd6..1f0b22b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1325,6 +1325,7 @@
<java-symbol type="dimen" name="lock_pattern_dot_line_width" />
<java-symbol type="dimen" name="lock_pattern_dot_size" />
<java-symbol type="dimen" name="lock_pattern_dot_size_activated" />
+ <java-symbol type="dimen" name="lock_pattern_dot_hit_factor" />
<java-symbol type="dimen" name="lock_pattern_fade_away_gradient_width" />
<java-symbol type="drawable" name="clock_dial" />
<java-symbol type="drawable" name="clock_hand_hour" />
@@ -4769,5 +4770,6 @@
<java-symbol type="integer" name="config_bg_current_drain_exempted_types" />
<java-symbol type="bool" name="config_bg_current_drain_high_threshold_by_bg_location" />
<java-symbol type="drawable" name="ic_swap_horiz" />
+ <java-symbol type="bool" name="config_notificationForceUserSetOnUpgrade" />
</resources>
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 5c9044c..beadc446 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -424,6 +424,7 @@
@Override
public void bindApplication(String s, ApplicationInfo applicationInfo,
+ String sdkSandboxClientAppPackage,
ProviderInfoList list, ComponentName componentName, ProfilerInfo profilerInfo,
Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 227a8657..c504f0c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -30,6 +30,7 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.LAST_TYPE;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -758,6 +759,11 @@
@Test
public void testCaptionInsetsStateAssemble() {
+ if (CAPTION_ON_SHELL) {
+ // For this case, the test is covered by WindowContainerInsetsSourceProviderTest, This
+ // test can be removed after the caption is moved to shell completely.
+ return;
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.onFrameChanged(new Rect(0, 0, 100, 300));
final InsetsState state = new InsetsState(mController.getState(), true);
@@ -769,6 +775,7 @@
assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame());
assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/,
true /* excludeInvisibleIme */));
+ // Test update to remove the caption bar
mController.setCaptionInsetsHeight(0);
mController.onStateChanged(state);
// The caption bar source should not be there at all, because we don't add empty
@@ -779,6 +786,11 @@
@Test
public void testNotifyCaptionInsetsOnlyChange() {
+ if (CAPTION_ON_SHELL) {
+ // For this case, the test is covered by WindowContainerInsetsSourceProviderTest, This
+ // test can be removed after the caption is moved to shell completely.
+ return;
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
final InsetsState state = new InsetsState(mController.getState(), true);
reset(mTestHost);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 0e394c1..bfb3449 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -30,6 +30,7 @@
public class BatteryStatsSensorTest extends TestCase {
private static final int UID = 10500;
+ private static final int UID_2 = 10501; // second uid for testing pool usage
private static final int SENSOR_ID = -10000;
@SmallTest
@@ -239,7 +240,6 @@
@SmallTest
public void testPooledBackgroundUsage() throws Exception {
- final int UID_2 = 20000; // second uid for testing pool usage
final MockClock clocks = new MockClock();
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
bi.mForceOnBattery = true;
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternViewTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternViewTest.java
new file mode 100644
index 0000000..8ba4966
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternViewTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+
+import androidx.test.annotation.UiThreadTest;
+
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.UiThreadTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import com.android.internal.R;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+@SmallTest
+public class LockPatternViewTest {
+
+ @Rule
+ public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
+
+ private final int mViewSize;
+ private final float mDefaultError;
+ private final float mDot1x;
+ private final float mDot1y;
+ private final float mDot2x;
+ private final float mDot2y;
+ private final float mDot3x;
+ private final float mDot3y;
+ private final float mDot5x;
+ private final float mDot5y;
+ private final float mDot7x;
+ private final float mDot7y;
+ private final float mDot9x;
+ private final float mDot9y;
+
+ private Context mContext;
+ private LockPatternView mLockPatternView;
+ @Mock
+ private LockPatternView.OnPatternListener mPatternListener;
+ @Captor
+ private ArgumentCaptor<List<LockPatternView.Cell>> mCellsArgumentCaptor;
+
+ public LockPatternViewTest(int viewSize) {
+ mViewSize = viewSize;
+ float cellSize = viewSize / 3f;
+ mDefaultError = cellSize * 0.2f;
+ mDot1x = cellSize / 2f;
+ mDot1y = cellSize / 2f;
+ mDot2x = cellSize + mDot1x;
+ mDot2y = mDot1y;
+ mDot3x = cellSize + mDot2x;
+ mDot3y = mDot1y;
+ // dot4 is skipped as redundant
+ mDot5x = cellSize + mDot1x;
+ mDot5y = cellSize + mDot1y;
+ // dot6 is skipped as redundant
+ mDot7x = mDot1x;
+ mDot7y = cellSize * 2 + mDot1y;
+ // dot8 is skipped as redundant
+ mDot9x = cellSize * 2 + mDot7x;
+ mDot9y = mDot7y;
+ }
+
+ @Parameterized.Parameters
+ public static Collection primeNumbers() {
+ return Arrays.asList(192, 512, 768, 1024);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getContext();
+ mLockPatternView = new LockPatternView(mContext, null);
+ int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(mViewSize,
+ View.MeasureSpec.EXACTLY);
+ int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(mViewSize,
+ View.MeasureSpec.EXACTLY);
+ mLockPatternView.measure(widthMeasureSpec, heightMeasureSpec);
+ mLockPatternView.layout(0, 0, mLockPatternView.getMeasuredWidth(),
+ mLockPatternView.getMeasuredHeight());
+ }
+
+ @UiThreadTest
+ @Test
+ public void downStartsPattern() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, mDot1x, mDot1y, 1));
+ verify(mPatternListener).onPatternStart();
+ }
+
+ @UiThreadTest
+ @Test
+ public void up_completesPattern() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, mDot1x, mDot1y, 1));
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, mDot1x, mDot1y, 1));
+ verify(mPatternListener).onPatternDetected(any());
+ }
+
+ @UiThreadTest
+ @Test
+ public void moveToDot_hitsDot() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1));
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, mDot1x, mDot1y, 1));
+ verify(mPatternListener).onPatternStart();
+ }
+
+ @UiThreadTest
+ @Test
+ public void moveOutside_doesNotHitsDot() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1));
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 2f, 2f, 1));
+ verify(mPatternListener, never()).onPatternStart();
+ }
+
+ @UiThreadTest
+ @Test
+ public void moveAlongTwoDots_hitsTwo() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1));
+ makeMove(mDot1x, mDot1y, mDot2x, mDot2y, 6);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, mDot2x, mDot2y, 1));
+
+ verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture());
+ List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue();
+ assertThat(patternCells, hasSize(2));
+ assertThat(patternCells,
+ contains(LockPatternView.Cell.of(0, 0), LockPatternView.Cell.of(0, 1)));
+ }
+
+ @UiThreadTest
+ @Test
+ public void moveAlongTwoDotsDiagonally_hitsTwo() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1));
+ makeMove(mDot1x, mDot1y, mDot5x, mDot5y, 6);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, mDot5x, mDot5y, 1));
+
+ verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture());
+ List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue();
+ assertThat(patternCells, hasSize(2));
+ assertThat(patternCells,
+ contains(LockPatternView.Cell.of(0, 0), LockPatternView.Cell.of(1, 1)));
+ }
+
+ @UiThreadTest
+ @Test
+ public void moveAlongZPattern_hitsDots() {
+ mLockPatternView.setOnPatternListener(mPatternListener);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1));
+ makeMove(mDot1x, mDot1y, mDot3x + mDefaultError, mDot3y, 10);
+ makeMove(mDot3x - mDefaultError, mDot3y, mDot7x, mDot7y, 10);
+ makeMove(mDot7x, mDot7y - mDefaultError, mDot9x, mDot9y - mDefaultError, 10);
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, mViewSize - mDefaultError,
+ mViewSize - mDefaultError, 1));
+
+ verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture());
+ List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue();
+ assertThat(patternCells, hasSize(7));
+ assertThat(patternCells,
+ contains(LockPatternView.Cell.of(0, 0),
+ LockPatternView.Cell.of(0, 1),
+ LockPatternView.Cell.of(0, 2),
+ LockPatternView.Cell.of(1, 1),
+ LockPatternView.Cell.of(2, 0),
+ LockPatternView.Cell.of(2, 1),
+ LockPatternView.Cell.of(2, 2)));
+ }
+
+ private void makeMove(float xFrom, float yFrom, float xTo, float yTo, int numberOfSteps) {
+ for (int i = 0; i < numberOfSteps; i++) {
+ float progress = i / (numberOfSteps - 1f);
+ float rest = 1f - progress;
+ mLockPatternView.onTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ /* x= */ xFrom * rest + xTo * progress,
+ /* y= */ yFrom * rest + yTo * progress,
+ 1));
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index b659f37..940ca96 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,14 +19,20 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
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.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;
@@ -50,6 +56,7 @@
import org.mockito.Mockito;
import java.nio.charset.StandardCharsets;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -164,6 +171,16 @@
verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
}
+ @Test
+ public void testGetEnabledTrustAgentsNotNull() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn("");
+ List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+ assertNotNull(trustAgents);
+ assertEquals(0, trustAgents.size());
+ }
+
private ILockSettings createTestLockSettings() {
final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mLockPatternUtils = spy(new LockPatternUtils(context));
diff --git a/data/etc/com.android.launcher3.xml b/data/etc/com.android.launcher3.xml
index 598d202..36a5134 100644
--- a/data/etc/com.android.launcher3.xml
+++ b/data/etc/com.android.launcher3.xml
@@ -16,6 +16,7 @@
-->
<permissions>
<privapp-permissions package="com.android.launcher3">
+ <permission name="android.permission.ALLOW_SLIPPERY_TOUCHES"/>
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ae350ec..d1873a0 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -17,6 +17,7 @@
<permissions>
<privapp-permissions package="com.android.systemui">
<permission name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
+ <permission name="android.permission.ALLOW_SLIPPERY_TOUCHES"/>
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 88920c8..a829339 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -241,7 +241,7 @@
</split-permission>
<split-permission name="android.permission.READ_EXTERNAL_STORAGE"
targetSdk="33">
- <new-permission name="android.permission.READ_MEDIA_IMAGE" />
+ <new-permission name="android.permission.READ_MEDIA_IMAGES" />
</split-permission>
<split-permission name="android.permission.BLUETOOTH"
targetSdk="31">
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index ffaa4ea..c1addbf 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -1098,19 +1098,16 @@
}
// Draw the appropriate mask anchored to (0,0).
+ final int saveCount = mMaskCanvas.save();
final int left = bounds.left;
final int top = bounds.top;
- if (mState.mRippleStyle == STYLE_SOLID) {
- mMaskCanvas.translate(-left, -top);
- }
+ mMaskCanvas.translate(-left, -top);
if (maskType == MASK_EXPLICIT) {
drawMask(mMaskCanvas);
} else if (maskType == MASK_CONTENT) {
drawContent(mMaskCanvas);
}
- if (mState.mRippleStyle == STYLE_SOLID) {
- mMaskCanvas.translate(left, top);
- }
+ mMaskCanvas.restoreToCount(saveCount);
if (mState.mRippleStyle == STYLE_PATTERNED) {
for (int i = 0; i < mRunningAnimations.size(); i++) {
mRunningAnimations.get(i).getProperties().getShader().setShader(mMaskShader);
@@ -1210,9 +1207,13 @@
updateMaskShaderIfNeeded();
// Position the shader to account for canvas translation.
- if (mMaskShader != null && mState.mRippleStyle == STYLE_SOLID) {
+ if (mMaskShader != null) {
final Rect bounds = getBounds();
- mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
+ if (mState.mRippleStyle == STYLE_PATTERNED) {
+ mMaskMatrix.setTranslate(bounds.left, bounds.top);
+ } else {
+ mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
+ }
mMaskShader.setLocalMatrix(mMaskMatrix);
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 31dd10a..e7961c9 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -108,6 +108,16 @@
}
}
+ /**
+ * XDH represents Curve 25519 providers.
+ */
+ public static class XDH extends AndroidKeyStoreKeyPairGeneratorSpi {
+ // XDH is treated as EC.
+ public XDH() {
+ super(KeymasterDefs.KM_ALGORITHM_EC);
+ }
+ }
+
/*
* These must be kept in sync with system/security/keystore/defaults.h
*/
@@ -242,6 +252,23 @@
} catch (NullPointerException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
+ } else if (params instanceof NamedParameterSpec) {
+ NamedParameterSpec namedSpec = (NamedParameterSpec) params;
+ // Android Keystore cannot support initialization from a NamedParameterSpec
+ // because an alias for the key is needed (a KeyGenParameterSpec cannot be
+ // constructed).
+ if (namedSpec.getName().equalsIgnoreCase(NamedParameterSpec.X25519.getName())
+ || namedSpec.getName().equalsIgnoreCase(
+ NamedParameterSpec.ED25519.getName())) {
+ throw new IllegalArgumentException(
+ "This KeyPairGenerator cannot be initialized using NamedParameterSpec."
+ + " use " + KeyGenParameterSpec.class.getName() + " or "
+ + KeyPairGeneratorSpec.class.getName());
+ } else {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported algorithm specified via NamedParameterSpec: "
+ + namedSpec.getName());
+ }
} else {
throw new InvalidAlgorithmParameterException(
"Unsupported params class: " + params.getClass().getName()
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index e5d1276..d31499e 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -83,10 +83,12 @@
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+ put("KeyPairGenerator.XDH", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$XDH");
// java.security.KeyFactory
putKeyFactoryImpl("EC");
putKeyFactoryImpl("RSA");
+ putKeyFactoryImpl("XDH");
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index 89d7a40..b3becad 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -33,6 +33,12 @@
* The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
*/
class TaskFragmentAnimationAdapter {
+
+ /**
+ * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
+ */
+ private static final int LAYER_NO_OVERRIDE = -1;
+
final Animation mAnimation;
final RemoteAnimationTarget mTarget;
final SurfaceControl mLeash;
@@ -42,6 +48,7 @@
final float[] mVecs = new float[4];
final Rect mRect = new Rect();
private boolean mIsFirstFrame = true;
+ private int mOverrideLayer = LAYER_NO_OVERRIDE;
TaskFragmentAnimationAdapter(@NonNull Animation animation,
@NonNull RemoteAnimationTarget target) {
@@ -58,10 +65,21 @@
mLeash = leash;
}
+ /**
+ * Surface layer to be set at the first frame of the animation. We will not set the layer if it
+ * is set to {@link #LAYER_NO_OVERRIDE}.
+ */
+ final void overrideLayer(int layer) {
+ mOverrideLayer = layer;
+ }
+
/** Called on frame update. */
final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
if (mIsFirstFrame) {
t.show(mLeash);
+ if (mOverrideLayer != LAYER_NO_OVERRIDE) {
+ t.setLayer(mLeash, mOverrideLayer);
+ }
mIsFirstFrame = false;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 46bdf6d..1ac3317 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import android.animation.Animator;
import android.animation.ValueAnimator;
@@ -181,18 +182,22 @@
private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
- return createOpenCloseAnimationAdapters(targets,
+ return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
mAnimationSpec::loadOpenAnimation);
}
private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
- return createOpenCloseAnimationAdapters(targets,
+ return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
mAnimationSpec::loadCloseAnimation);
}
+ /**
+ * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
+ * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
+ */
private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets,
+ @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
// We need to know if the target window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one.
@@ -210,14 +215,25 @@
}
}
+ // For OPEN transition, open windows should be above close windows.
+ // For CLOSE transition, open windows should be below close windows.
+ int offsetLayer = TYPE_LAYER_OFFSET;
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
for (RemoteAnimationTarget target : openingTargets) {
- adapters.add(createOpenCloseAnimationAdapter(target, animationProvider,
- openingWholeScreenBounds));
+ final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
+ animationProvider, openingWholeScreenBounds);
+ if (isOpening) {
+ adapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(adapter);
}
for (RemoteAnimationTarget target : closingTargets) {
- adapters.add(createOpenCloseAnimationAdapter(target, animationProvider,
- closingWholeScreenBounds));
+ final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
+ animationProvider, closingWholeScreenBounds);
+ if (!isOpening) {
+ adapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(adapter);
}
return adapters;
}
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index 552048e..dcb4c10 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -33,4 +33,10 @@
<!-- The default gravity for the picture-in-picture window.
Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
<integer name="config_defaultPictureInPictureGravity">0x55</integer>
+
+ <!-- Fraction of screen width/height restricted keep clear areas can move the PiP. -->
+ <fraction name="config_pipMaxRestrictedMoveDistance">15%</fraction>
+
+ <!-- Duration (in milliseconds) the PiP stays stashed before automatically unstashing. -->
+ <integer name="config_pipStashDuration">5000</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml
new file mode 100644
index 0000000..14e89f8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-television/dimen.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for TV products. Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Padding between PIP and keep clear areas that caused it to move. -->
+ <dimen name="pip_keep_clear_area_padding">16dp</dimen>
+</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 908a31d..8483f07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -21,6 +21,7 @@
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -46,11 +47,13 @@
private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<RecentTasksController> mRecentTasks;
private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final ShellExecutor mMainExecutor;
private final HandlerImpl mImpl = new HandlerImpl();
public ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
@@ -60,6 +63,7 @@
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
+ mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mRecentTasks = recentTasks;
mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
@@ -92,6 +96,9 @@
pw.println();
pw.println();
mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, ""));
+ pw.println();
+ pw.println();
+ mKidsModeTaskOrganizer.dump(pw, "");
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index c3ce362..b729fe1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -29,6 +29,7 @@
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -48,6 +49,7 @@
private final DisplayInsetsController mDisplayInsetsController;
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final Optional<BubbleController> mBubblesOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<AppPairsController> mAppPairsOptional;
@@ -68,6 +70,7 @@
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
@@ -84,6 +87,7 @@
mDisplayInsetsController = displayInsetsController;
mDragAndDropController = dragAndDropController;
mShellTaskOrganizer = shellTaskOrganizer;
+ mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mBubblesOptional = bubblesOptional;
mSplitScreenOptional = splitScreenOptional;
mAppPairsOptional = appPairsOptional;
@@ -136,6 +140,9 @@
mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
mRecentTasks.ifPresent(RecentTasksController::init);
+
+ // Initialize kids mode task organizer
+ mKidsModeTaskOrganizer.initialize(mStartingWindow);
}
@ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 3f8343a..8442994 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -196,8 +196,8 @@
}
@VisibleForTesting
- ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context, @Nullable CompatUIController compatUI,
+ protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
+ ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
Optional<RecentTasksController> recentTasks) {
super(taskOrganizerController, mainExecutor);
mCompatUI = compatUI;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index d22fb50..4ba32e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -110,6 +110,14 @@
}
/**
+ * Get the InsetsState of a display.
+ */
+ public InsetsState getInsetsState(int displayId) {
+ final DisplayRecord r = mDisplays.get(displayId);
+ return r != null ? r.mInsetsState : null;
+ }
+
+ /**
* Updates the insets for a given display.
*/
public void updateDisplayInsets(int displayId, InsetsState state) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 1e934c5..1dd5ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -69,7 +69,8 @@
TaskStackListenerImpl taskStackListener,
DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
return Optional.of(
TvPipController.create(
context,
@@ -83,7 +84,8 @@
taskStackListener,
displayController,
windowManagerShellWrapper,
- mainExecutor));
+ mainExecutor,
+ mainHandler));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 0362b3f..bf0337d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -68,6 +68,7 @@
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
@@ -184,7 +185,23 @@
@WMSingleton
@Provides
- static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
+ static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
+ Context context,
+ CompatUIController compatUI,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<RecentTasksController> recentTasksOptional
+ ) {
+ return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, compatUI,
+ syncTransactionQueue, displayController, displayInsetsController,
+ recentTasksOptional);
+ }
+
+ @WMSingleton
+ @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
return Optional.of(compatUIController.asCompatUI());
}
@@ -637,6 +654,7 @@
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
@@ -653,6 +671,7 @@
displayInsetsController,
dragAndDropController,
shellTaskOrganizer,
+ kidsModeTaskOrganizer,
bubblesOptional,
splitScreenOptional,
appPairsOptional,
@@ -680,6 +699,7 @@
@Provides
static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
@@ -688,7 +708,7 @@
Optional<AppPairsController> appPairsOptional,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandlerImpl(shellTaskOrganizer,
+ return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
new file mode 100644
index 0000000..429eb99
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -0,0 +1,341 @@
+/*
+ * 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.wm.shell.kidsmode;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.ForceShowNavigationBarSettingsObserver;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.startingsurface.StartingWindowController;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * A dedicated task organizer when kids mode is enabled.
+ * - Creates a root task with bounds that exclude the navigation bar area
+ * - Launch all task into the root task except for Launcher
+ */
+public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
+ private static final String TAG = "KidsModeTaskOrganizer";
+
+ private static final int[] CONTROLLED_ACTIVITY_TYPES =
+ {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD};
+ private static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+
+ private final Handler mMainHandler;
+ private final Context mContext;
+ private final SyncTransactionQueue mSyncQueue;
+ private final DisplayController mDisplayController;
+ private final DisplayInsetsController mDisplayInsetsController;
+
+ @VisibleForTesting
+ ActivityManager.RunningTaskInfo mLaunchRootTask;
+ @VisibleForTesting
+ SurfaceControl mLaunchRootLeash;
+ @VisibleForTesting
+ final IBinder mCookie = new Binder();
+
+ private final InsetsState mInsetsState = new InsetsState();
+ private int mDisplayWidth;
+ private int mDisplayHeight;
+
+ private ForceShowNavigationBarSettingsObserver mForceShowNavigationBarSettingsObserver;
+ private boolean mEnabled;
+
+ DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
+ if (displayLayout == null) {
+ return;
+ }
+ final int displayWidth = displayLayout.width();
+ final int displayHeight = displayLayout.height();
+ if (displayWidth == mDisplayWidth || displayHeight == mDisplayHeight) {
+ return;
+ }
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ updateBounds();
+ }
+ };
+
+ DisplayInsetsController.OnInsetsChangedListener mOnInsetsChangedListener =
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ // Update bounds only when the insets of navigation bar or task bar is changed.
+ if (Objects.equals(insetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR),
+ mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR))
+ && Objects.equals(insetsState.peekSource(
+ InsetsState.ITYPE_EXTRA_NAVIGATION_BAR),
+ mInsetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR))) {
+ return;
+ }
+ mInsetsState.set(insetsState);
+ updateBounds();
+ }
+ };
+
+ @VisibleForTesting
+ KidsModeTaskOrganizer(
+ ITaskOrganizerController taskOrganizerController,
+ ShellExecutor mainExecutor,
+ Handler mainHandler,
+ Context context,
+ @Nullable CompatUIController compatUI,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<RecentTasksController> recentTasks,
+ ForceShowNavigationBarSettingsObserver forceShowNavigationBarSettingsObserver) {
+ super(taskOrganizerController, mainExecutor, context, compatUI, recentTasks);
+ mContext = context;
+ mMainHandler = mainHandler;
+ mSyncQueue = syncTransactionQueue;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ mForceShowNavigationBarSettingsObserver = forceShowNavigationBarSettingsObserver;
+ }
+
+ public KidsModeTaskOrganizer(
+ ShellExecutor mainExecutor,
+ Handler mainHandler,
+ Context context,
+ @Nullable CompatUIController compatUI,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<RecentTasksController> recentTasks) {
+ super(mainExecutor, context, compatUI, recentTasks);
+ mContext = context;
+ mMainHandler = mainHandler;
+ mSyncQueue = syncTransactionQueue;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ }
+
+ /**
+ * Initializes kids mode status.
+ */
+ public void initialize(StartingWindowController startingWindowController) {
+ initStartingWindow(startingWindowController);
+ if (mForceShowNavigationBarSettingsObserver == null) {
+ mForceShowNavigationBarSettingsObserver = new ForceShowNavigationBarSettingsObserver(
+ mMainHandler, mContext);
+ }
+ mForceShowNavigationBarSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState());
+ updateKidsModeState();
+ mForceShowNavigationBarSettingsObserver.register();
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mEnabled && mLaunchRootTask == null && taskInfo.launchCookies != null
+ && taskInfo.launchCookies.contains(mCookie)) {
+ mLaunchRootTask = taskInfo;
+ mLaunchRootLeash = leash;
+ updateTask();
+ }
+ super.onTaskAppeared(taskInfo, leash);
+
+ mSyncQueue.runInSync(t -> {
+ // Reset several properties back to fullscreen (PiP, for example, leaves all these
+ // properties in a bad state).
+ t.setCrop(leash, null);
+ t.setPosition(leash, 0, 0);
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ });
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mLaunchRootTask != null && mLaunchRootTask.taskId == taskInfo.taskId
+ && !taskInfo.equals(mLaunchRootTask)) {
+ mLaunchRootTask = taskInfo;
+ }
+
+ super.onTaskInfoChanged(taskInfo);
+ }
+
+ @VisibleForTesting
+ void updateKidsModeState() {
+ final boolean enabled = mForceShowNavigationBarSettingsObserver.isEnabled();
+ if (mEnabled == enabled) {
+ return;
+ }
+ mEnabled = enabled;
+ if (mEnabled) {
+ enable();
+ } else {
+ disable();
+ }
+ }
+
+ @VisibleForTesting
+ void enable() {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
+ if (displayLayout != null) {
+ mDisplayWidth = displayLayout.width();
+ mDisplayHeight = displayLayout.height();
+ }
+ mInsetsState.set(mDisplayController.getInsetsState(DEFAULT_DISPLAY));
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY,
+ mOnInsetsChangedListener);
+ mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+ List<TaskAppearedInfo> taskAppearedInfos = registerOrganizer();
+ for (int i = 0; i < taskAppearedInfos.size(); i++) {
+ final TaskAppearedInfo info = taskAppearedInfos.get(i);
+ onTaskAppeared(info.getTaskInfo(), info.getLeash());
+ }
+ createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, mCookie);
+ updateTask();
+ }
+
+ @VisibleForTesting
+ void disable() {
+ mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
+ mOnInsetsChangedListener);
+ mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ updateTask();
+ final WindowContainerToken token = mLaunchRootTask.token;
+ if (token != null) {
+ deleteRootTask(token);
+ }
+ mLaunchRootTask = null;
+ mLaunchRootLeash = null;
+ unregisterOrganizer();
+ }
+
+ private void updateTask() {
+ updateTask(getWindowContainerTransaction());
+ }
+
+ private void updateTask(WindowContainerTransaction wct) {
+ if (mLaunchRootTask == null || mLaunchRootLeash == null) {
+ return;
+ }
+ final Rect taskBounds = calculateBounds();
+ final WindowContainerToken rootToken = mLaunchRootTask.token;
+ wct.setBounds(rootToken, mEnabled ? taskBounds : null);
+ wct.setLaunchRoot(rootToken,
+ mEnabled ? CONTROLLED_WINDOWING_MODES : null,
+ mEnabled ? CONTROLLED_ACTIVITY_TYPES : null);
+ wct.reparentTasks(
+ mEnabled ? null : rootToken /* currentParent */,
+ mEnabled ? rootToken : null /* newParent */,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */);
+ wct.reorder(rootToken, mEnabled /* onTop */);
+ mSyncQueue.queue(wct);
+ final SurfaceControl rootLeash = mLaunchRootLeash;
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
+ t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
+ });
+ }
+
+ private Rect calculateBounds() {
+ final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight);
+ final InsetsSource navBarSource = mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR);
+ final InsetsSource taskBarSource = mInsetsState.peekSource(
+ InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ if (navBarSource != null && !navBarSource.getFrame().isEmpty()) {
+ bounds.inset(navBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
+ } else if (taskBarSource != null && !taskBarSource.getFrame().isEmpty()) {
+ bounds.inset(taskBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
+ } else {
+ bounds.setEmpty();
+ }
+ return bounds;
+ }
+
+ private void updateBounds() {
+ if (mLaunchRootTask == null) {
+ return;
+ }
+ final WindowContainerTransaction wct = getWindowContainerTransaction();
+ final Rect taskBounds = calculateBounds();
+ wct.setBounds(mLaunchRootTask.token, taskBounds);
+ mSyncQueue.queue(wct);
+ final SurfaceControl finalLeash = mLaunchRootLeash;
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
+ t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height());
+ });
+ }
+
+ @VisibleForTesting
+ WindowContainerTransaction getWindowContainerTransaction() {
+ return new WindowContainerTransaction();
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + " mEnabled=" + mEnabled);
+ pw.println(innerPrefix + " mLaunchRootTask=" + mLaunchRootTask);
+ pw.println(innerPrefix + " mLaunchRootLeash=" + mLaunchRootLeash);
+ pw.println(innerPrefix + " mDisplayWidth=" + mDisplayWidth);
+ pw.println(innerPrefix + " mDisplayHeight=" + mDisplayHeight);
+ pw.println(innerPrefix + " mInsetsState=" + mInsetsState);
+ super.dump(pw, innerPrefix);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index 797df41..c2d5823 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -74,7 +74,7 @@
/**
* TODO: move the resources to SysUI package.
*/
- protected void reloadResources(Context context) {
+ private void reloadResources(Context context) {
final Resources res = context.getResources();
mDefaultAspectRatio = res.getFloat(
R.dimen.config_pictureInPictureDefaultAspectRatio);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index ddcd4bd..17d7f5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -55,11 +55,15 @@
public static final int STASH_TYPE_NONE = 0;
public static final int STASH_TYPE_LEFT = 1;
public static final int STASH_TYPE_RIGHT = 2;
+ public static final int STASH_TYPE_BOTTOM = 3;
+ public static final int STASH_TYPE_TOP = 4;
@IntDef(prefix = { "STASH_TYPE_" }, value = {
STASH_TYPE_NONE,
STASH_TYPE_LEFT,
- STASH_TYPE_RIGHT
+ STASH_TYPE_RIGHT,
+ STASH_TYPE_BOTTOM,
+ STASH_TYPE_TOP
})
@Retention(RetentionPolicy.SOURCE)
public @interface StashType {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index e57abc2..0f3ff36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -148,6 +148,7 @@
mMainExecutor.execute(() -> {
// Choreographer.getSfInstance() must be called on the thread that the input event
// receiver should be receiving events
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
mInputEventReceiver = new InputEventReceiver(inputChannel,
Looper.myLooper(), Choreographer.getSfInstance());
if (mRegistrationListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index f3789fd..fa0f092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -95,6 +95,8 @@
final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() {
@Override
public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) {
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for
+ // transactions
Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index c816f18..abf1a95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -625,6 +625,7 @@
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
super(channel, looper, Choreographer.getSfInstance());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 8ab78e6..d6dacd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -28,15 +28,22 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.util.Log;
+import android.os.SystemClock;
+import android.util.ArraySet;
import android.util.Size;
import android.view.Gravity;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.Set;
/**
* Contains pip bounds calculations that are specific to TV.
@@ -46,91 +53,148 @@
private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName();
private static final boolean DEBUG = TvPipController.DEBUG;
- private final @android.annotation.NonNull TvPipBoundsState mTvPipBoundsState;
+ private final @NonNull TvPipBoundsState mTvPipBoundsState;
private int mFixedExpandedHeightInPx;
private int mFixedExpandedWidthInPx;
+ private final TvPipKeepClearAlgorithm mKeepClearAlgorithm;
+
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
super(context, tvPipBoundsState, pipSnapAlgorithm);
this.mTvPipBoundsState = tvPipBoundsState;
+ this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis);
+ reloadResources(context);
}
- @Override
- protected void reloadResources(Context context) {
- super.reloadResources(context);
+ private void reloadResources(Context context) {
final Resources res = context.getResources();
mFixedExpandedHeightInPx = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_pictureInPictureExpandedHorizontalHeight);
mFixedExpandedWidthInPx = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_pictureInPictureExpandedVerticalWidth);
+ mKeepClearAlgorithm.setPipAreaPadding(
+ res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding));
+ mKeepClearAlgorithm.setMaxRestrictedDistanceFraction(
+ res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1));
+ mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration));
+ }
+
+ @Override
+ public void onConfigurationChanged(Context context) {
+ super.onConfigurationChanged(context);
+ reloadResources(context);
}
/** Returns the destination bounds to place the PIP window on entry. */
@Override
public Rect getEntryDestinationBounds() {
- if (DEBUG) Log.d(TAG, "getEntryDestinationBounds()");
- if (mTvPipBoundsState.getTvExpandedAspectRatio() != 0
- && !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
- updatePositionOnExpandToggled(Gravity.NO_GRAVITY, true);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getEntryDestinationBounds()", TAG);
}
- return getTvPipBounds(true);
+ if (mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
+ && !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
+ updateExpandedPipSize();
+ updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
+ }
+ return getTvPipBounds().getBounds();
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@Override
public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) {
- if (DEBUG) Log.d(TAG, "getAdjustedDestinationBounds: " + newAspectRatio);
- return getTvPipBounds(mTvPipBoundsState.isTvPipExpanded());
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
+ }
+ return getTvPipBounds().getBounds();
}
/**
- * The normal bounds at a different position on the screen.
+ * Calculates the PiP bounds.
*/
- public Rect getTvNormalBounds() {
- Rect normalBounds = getNormalBounds();
- Rect insetBounds = new Rect();
+ public Placement getTvPipBounds() {
+ final Size pipSize = getPipSize();
+ final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
+ final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
+ final Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
+ Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
+ Set<Rect> unrestrictedKeepClearAreas = mTvPipBoundsState.getUnrestrictedKeepClearAreas();
+
if (mTvPipBoundsState.isImeShowing()) {
- if (DEBUG) Log.d(TAG, "IME showing, height: " + mTvPipBoundsState.getImeHeight());
- insetBounds.bottom -= mTvPipBoundsState.getImeHeight();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: IME showing, height: %d",
+ TAG, mTvPipBoundsState.getImeHeight());
+ }
+
+ final Rect imeBounds = new Rect(
+ 0,
+ insetBounds.bottom - mTvPipBoundsState.getImeHeight(),
+ insetBounds.right,
+ insetBounds.bottom);
+
+ unrestrictedKeepClearAreas = new ArraySet<>(unrestrictedKeepClearAreas);
+ unrestrictedKeepClearAreas.add(imeBounds);
}
- Rect result = new Rect();
- Gravity.apply(mTvPipBoundsState.getTvPipGravity(), normalBounds.width(),
- normalBounds.height(), insetBounds, result);
+ mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity());
+ mKeepClearAlgorithm.setScreenSize(screenSize);
+ mKeepClearAlgorithm.setMovementBounds(insetBounds);
+ mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
+
+ final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
+ pipSize,
+ restrictedKeepClearAreas,
+ unrestrictedKeepClearAreas);
if (DEBUG) {
- Log.d(TAG, "normalBounds: " + normalBounds.toShortString());
- Log.d(TAG, "insetBounds: " + insetBounds.toShortString());
- Log.d(TAG, "gravity: " + Gravity.toString(mTvPipBoundsState.getTvPipGravity()));
- Log.d(TAG, "resultBounds: " + result.toShortString());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: screenSize: %s", TAG, screenSize);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: stashOffset: %d", TAG, mTvPipBoundsState.getStashOffset());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: insetBounds: %s", TAG, insetBounds.toShortString());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: pipSize: %s", TAG, pipSize);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: gravity: %s", TAG, Gravity.toString(mTvPipBoundsState.getTvPipGravity()));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: restrictedKeepClearAreas: %s", TAG, restrictedKeepClearAreas);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: unrestrictedKeepClearAreas: %s", TAG, unrestrictedKeepClearAreas);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: placement: %s", TAG, placement);
}
- mTvPipBoundsState.setTvPipExpanded(false);
-
- return result;
+ return placement;
}
/**
- * @return previous gravity if it is to be saved, or Gravity.NO_GRAVITY if not.
+ * @return previous gravity if it is to be saved, or {@link Gravity#NO_GRAVITY} if not.
*/
- int updatePositionOnExpandToggled(int previousGravity, boolean expanding) {
+ int updateGravityOnExpandToggled(int previousGravity, boolean expanding) {
if (DEBUG) {
- Log.d(TAG, "updatePositionOnExpandToggle(), expanding: " + expanding
- + ", mOrientation: " + mTvPipBoundsState.getTvFixedPipOrientation()
- + ", previous gravity: " + Gravity.toString(previousGravity));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravityOnExpandToggled(), expanding: %b"
+ + ", mOrientation: %d, previous gravity: %s",
+ TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation(),
+ Gravity.toString(previousGravity));
}
- if (!mTvPipBoundsState.isTvExpandedPipEnabled()) {
+ if (!mTvPipBoundsState.isTvExpandedPipSupported()) {
return Gravity.NO_GRAVITY;
}
if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) {
- float expandedRatio = mTvPipBoundsState.getTvExpandedAspectRatio();
+ float expandedRatio = mTvPipBoundsState.getDesiredTvExpandedAspectRatio();
if (expandedRatio == 0) {
return Gravity.NO_GRAVITY;
}
@@ -139,7 +203,6 @@
} else {
mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL);
}
-
}
int gravityToSave = Gravity.NO_GRAVITY;
@@ -175,16 +238,22 @@
}
}
mTvPipBoundsState.setTvPipGravity(updatedGravity);
- if (DEBUG) Log.d(TAG, "new gravity: " + Gravity.toString(updatedGravity));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
+ }
return gravityToSave;
}
/**
- * @return true if position changed
+ * @return true if gravity changed
*/
- boolean updatePosition(int keycode) {
- if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode);
+ boolean updateGravity(int keycode) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravity, keycode: %d", TAG, keycode);
+ }
// Check if position change is valid
if (mTvPipBoundsState.isTvPipExpanded()) {
@@ -241,32 +310,42 @@
if (updatedGravity != currentGravity) {
mTvPipBoundsState.setTvPipGravity(updatedGravity);
- if (DEBUG) Log.d(TAG, "new gravity: " + Gravity.toString(updatedGravity));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
+ }
return true;
}
return false;
}
+ private Size getPipSize() {
+ final boolean isExpanded =
+ mTvPipBoundsState.isTvExpandedPipSupported() && mTvPipBoundsState.isTvPipExpanded()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0;
+ if (isExpanded) {
+ return mTvPipBoundsState.getTvExpandedSize();
+ } else {
+ final Rect normalBounds = getNormalBounds();
+ return new Size(normalBounds.width(), normalBounds.height());
+ }
+ }
+
/**
- * Calculates the PiP bounds.
+ * Updates {@link TvPipBoundsState#getTvExpandedSize()} based on
+ * {@link TvPipBoundsState#getDesiredTvExpandedAspectRatio()}, the screen size.
*/
- public Rect getTvPipBounds(boolean expandedIfPossible) {
- if (DEBUG) {
- Log.d(TAG, "getExpandedBoundsIfPossible with gravity "
- + Gravity.toString(mTvPipBoundsState.getTvPipGravity())
- + ", fixed orientation: " + mTvPipBoundsState.getTvFixedPipOrientation());
- }
+ void updateExpandedPipSize() {
+ final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
+ final float expandedRatio =
+ mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
- if (!mTvPipBoundsState.isTvExpandedPipEnabled() || !expandedIfPossible) {
- return getTvNormalBounds();
- }
-
- DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
- float expandedRatio = mTvPipBoundsState.getTvExpandedAspectRatio(); // width / height
- Size expandedSize;
+ final Size expandedSize;
if (expandedRatio == 0) {
- Log.d(TAG, "Expanded mode not supported");
- return getTvNormalBounds();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpandedPipSize(): Expanded mode aspect ratio"
+ + " of 0 not supported", TAG);
+ return;
} else if (expandedRatio < 1) {
// vertical
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
@@ -276,10 +355,16 @@
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) {
- if (DEBUG) Log.d(TAG, "Accommodate aspect ratio");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Accommodate aspect ratio", TAG);
+ }
expandedSize = new Size(mFixedExpandedWidthInPx, (int) aspectRatioHeight);
} else {
- if (DEBUG) Log.d(TAG, "Aspect ratio is too extreme, use max size");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Aspect ratio is too extreme, use max size", TAG);
+ }
expandedSize = new Size(mFixedExpandedWidthInPx, maxHeight);
}
}
@@ -291,35 +376,30 @@
int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
- if (DEBUG) Log.d(TAG, "Accommodate aspect ratio");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Accommodate aspect ratio", TAG);
+ }
expandedSize = new Size((int) aspectRatioWidth, mFixedExpandedHeightInPx);
} else {
- if (DEBUG) Log.d(TAG, "Aspect ratio is too extreme, use max size");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Aspect ratio is too extreme, use max size", TAG);
+ }
expandedSize = new Size(maxWidth, mFixedExpandedHeightInPx);
}
}
}
- if (expandedSize == null) {
- return getTvNormalBounds();
- }
-
- if (DEBUG) {
- Log.d(TAG, "expanded size, width: " + expandedSize.getWidth()
- + ", height: " + expandedSize.getHeight());
- }
-
- Rect insetBounds = new Rect();
- getInsetBounds(insetBounds);
-
- Rect expandedBounds = new Rect();
- Gravity.apply(mTvPipBoundsState.getTvPipGravity(), expandedSize.getWidth(),
- expandedSize.getHeight(), insetBounds, expandedBounds);
- if (DEBUG) Log.d(TAG, "expanded bounds: " + expandedBounds.toShortString());
-
mTvPipBoundsState.setTvExpandedSize(expandedSize);
- mTvPipBoundsState.setTvPipExpanded(true);
- return expandedBounds;
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpandedPipSize(): expanded size, width: %d, height: %d",
+ TAG, expandedSize.getWidth(), expandedSize.getHeight());
+ }
}
+ void keepUnstashedForCurrentKeepClearAreas() {
+ mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 9370e33..d880f82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -53,10 +53,10 @@
public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
- private boolean mIsTvExpandedPipEnabled;
+ private final boolean mIsTvExpandedPipSupported;
private boolean mIsTvPipExpanded;
private boolean mTvPipManuallyCollapsed;
- private float mTvExpandedAspectRatio;
+ private float mDesiredTvExpandedAspectRatio;
private @Orientation int mTvFixedPipOrientation;
private int mTvPipGravity;
private @Nullable Size mTvExpandedSize;
@@ -64,8 +64,8 @@
public TvPipBoundsState(@NonNull Context context) {
super(context);
- setIsTvExpandedPipEnabled(context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE));
+ mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
}
/**
@@ -75,7 +75,7 @@
public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) {
super.setBoundsStateForEntry(componentName, activityInfo, params, pipBoundsAlgorithm);
- setTvExpandedAspectRatio(params.getExpandedAspectRatio(), true);
+ setDesiredTvExpandedAspectRatio(params.getExpandedAspectRatio(), true);
}
/** Resets the TV PiP state for a new activity. */
@@ -85,32 +85,32 @@
}
/** Set the tv expanded bounds of PIP */
- public void setTvExpandedSize(@Nullable Size bounds) {
- mTvExpandedSize = bounds;
+ public void setTvExpandedSize(@Nullable Size size) {
+ mTvExpandedSize = size;
}
- /** Get the PIP tv expanded bounds. */
+ /** Get the expanded size of the PiP. */
@Nullable
public Size getTvExpandedSize() {
return mTvExpandedSize;
}
/** Set the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
- public void setTvExpandedAspectRatio(float aspectRatio, boolean override) {
+ public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) {
if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED || aspectRatio == 0) {
- mTvExpandedAspectRatio = aspectRatio;
+ mDesiredTvExpandedAspectRatio = aspectRatio;
resetTvPipState();
return;
}
if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
|| (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)) {
- mTvExpandedAspectRatio = aspectRatio;
+ mDesiredTvExpandedAspectRatio = aspectRatio;
}
}
/** Get the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
- public float getTvExpandedAspectRatio() {
- return mTvExpandedAspectRatio;
+ public float getDesiredTvExpandedAspectRatio() {
+ return mDesiredTvExpandedAspectRatio;
}
/** Sets the orientation the expanded TV PiP activity has been fixed to. */
@@ -154,13 +154,9 @@
return mTvPipManuallyCollapsed;
}
- /** Sets whether expanded PiP is supported by the device. */
- public void setIsTvExpandedPipEnabled(boolean enabled) {
- mIsTvExpandedPipEnabled = enabled;
+ /** Returns whether expanded PiP is supported by the device. */
+ public boolean isTvExpandedPipSupported() {
+ return mIsTvExpandedPipSupported;
}
- /** Returns whether expanded PiP is supported by the device. */
- public boolean isTvExpandedPipEnabled() {
- return mIsTvExpandedPipEnabled;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 3c830e0..f397ac0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -30,11 +30,11 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
-import android.util.Log;
-import android.view.DisplayInfo;
import android.view.Gravity;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -45,9 +45,12 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -62,6 +65,7 @@
private static final String TAG = "TvPipController";
static final boolean DEBUG = false;
+ private static final double EPS = 1e-7;
private static final int NONEXISTENT_TASK_ID = -1;
@Retention(RetentionPolicy.SOURCE)
@@ -97,11 +101,13 @@
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP;
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
+ private Runnable mUnstashRunnable;
private int mResizeAnimationDuration;
@@ -117,7 +123,8 @@
TaskStackListenerImpl taskStackListener,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
return new TvPipController(
context,
tvPipBoundsState,
@@ -130,7 +137,8 @@
taskStackListener,
displayController,
wmShell,
- mainExecutor).mImpl;
+ mainExecutor,
+ mainHandler).mImpl;
}
private TvPipController(
@@ -145,9 +153,11 @@
TaskStackListenerImpl taskStackListener,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
mContext = context;
mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
@@ -173,15 +183,22 @@
}
private void onConfigurationChanged(Configuration newConfig) {
- if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
+ }
if (isPipShown()) {
- if (DEBUG) Log.d(TAG, " > closing Pip.");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > closing Pip.", TAG);
+ }
closePip();
}
loadConfigurations();
mPipNotificationController.onConfigurationChanged(mContext);
+ mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
/**
@@ -198,21 +215,37 @@
*/
@Override
public void showPictureInPictureMenu() {
- if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
+ }
if (mState == STATE_NO_PIP) {
- if (DEBUG) Log.d(TAG, " > cannot open Menu from the current state.");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > cannot open Menu from the current state.", TAG);
+ }
return;
}
setState(STATE_PIP_MENU);
- movePinnedStack();
+ updatePinnedStackBounds();
}
@Override
public void closeMenu() {
- if (DEBUG) Log.d(TAG, "closeMenu(), state before=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closeMenu(), state before=%s", TAG, stateToName(mState));
+ }
setState(STATE_PIP);
+ mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas();
+ updatePinnedStackBounds();
+ }
+
+ @Override
+ public void onInMoveModeChanged() {
+ updatePinnedStackBounds();
}
/**
@@ -220,7 +253,10 @@
*/
@Override
public void movePipToFullscreen() {
- if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
+ }
mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
onPipDisappeared();
@@ -228,26 +264,32 @@
@Override
public void togglePipExpansion() {
- if (DEBUG) Log.d(TAG, "togglePipExpansion()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: togglePipExpansion()", TAG);
+ }
boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
int saveGravity = mTvPipBoundsAlgorithm
- .updatePositionOnExpandToggled(mPreviousGravity, expanding);
+ .updateGravityOnExpandToggled(mPreviousGravity, expanding);
if (saveGravity != Gravity.NO_GRAVITY) {
mPreviousGravity = saveGravity;
}
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
- movePinnedStack();
+ updatePinnedStackBounds();
}
@Override
public void movePip(int keycode) {
- if (mTvPipBoundsAlgorithm.updatePosition(keycode)) {
+ if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
mPreviousGravity = Gravity.NO_GRAVITY;
- movePinnedStack();
+ updatePinnedStackBounds();
} else {
- if (DEBUG) Log.d(TAG, "Position hasn't changed");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Position hasn't changed", TAG);
+ }
}
}
@@ -265,23 +307,57 @@
Set<Rect> unrestricted) {
if (mTvPipBoundsState.getDisplayId() == displayId) {
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
- movePinnedStack();
+ updatePinnedStackBounds();
}
}
/**
- * Animate to the updated position of the PiP based on the state and position of the PiP.
+ * Update the PiP bounds based on the state of the PiP and keep clear areas.
+ * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
*/
- private void movePinnedStack() {
+ private void updatePinnedStackBounds() {
if (mState == STATE_NO_PIP) {
return;
}
- Rect bounds = mTvPipBoundsAlgorithm.getTvPipBounds(mTvPipBoundsState.isTvPipExpanded());
- if (DEBUG) Log.d(TAG, "movePinnedStack() - new pip bounds: " + bounds.toShortString());
+ final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode();
+ final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition;
+ final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds();
+
+ int stashType =
+ disallowStashing ? PipBoundsState.STASH_TYPE_NONE : placement.getStashType();
+ mTvPipBoundsState.setStashed(stashType);
+
+ if (stayAtAnchorPosition) {
+ movePinnedStackTo(placement.getAnchorBounds());
+ } else if (disallowStashing) {
+ movePinnedStackTo(placement.getUnstashedBounds());
+ } else {
+ movePinnedStackTo(placement.getBounds());
+ }
+
+ if (mUnstashRunnable != null) {
+ mMainHandler.removeCallbacks(mUnstashRunnable);
+ mUnstashRunnable = null;
+ }
+ if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
+ mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
+ mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
+ }
+ }
+
+ /** Animates the PiP to the given bounds. */
+ private void movePinnedStackTo(Rect bounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
+ }
mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
mResizeAnimationDuration, rect -> {
- if (DEBUG) Log.d(TAG, "movePinnedStack() animation done");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePinnedStack() animation done", TAG);
+ }
mTvPipMenuController.updateExpansionState();
});
}
@@ -291,7 +367,10 @@
*/
@Override
public void closePip() {
- if (DEBUG) Log.d(TAG, "closePip(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closePip(), state=%s", TAG, stateToName(mState));
+ }
removeTask(mPinnedTaskId);
onPipDisappeared();
@@ -303,7 +382,10 @@
private void checkIfPinnedTaskAppeared() {
final TaskInfo pinnedTask = getPinnedTaskInfo();
- if (DEBUG) Log.d(TAG, "checkIfPinnedTaskAppeared(), task=" + pinnedTask);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask);
+ }
if (pinnedTask == null || pinnedTask.topActivity == null) return;
mPinnedTaskId = pinnedTask.taskId;
@@ -312,16 +394,23 @@
}
private void checkIfPinnedTaskIsGone() {
- if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onTaskStackChanged()", TAG);
+ }
if (isPipShown() && getPinnedTaskInfo() == null) {
- Log.w(TAG, "Pinned task is gone.");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Pinned task is gone.", TAG);
onPipDisappeared();
}
}
private void onPipDisappeared() {
- if (DEBUG) Log.d(TAG, "onPipDisappeared() state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
+ }
mPipNotificationController.dismiss();
mTvPipMenuController.hideMenu();
@@ -332,12 +421,18 @@
@Override
public void onPipTransitionStarted(int direction, Rect pipBounds) {
- if (DEBUG) Log.d(TAG, "onPipTransition_Started(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
+ }
}
@Override
public void onPipTransitionCanceled(int direction) {
- if (DEBUG) Log.d(TAG, "onPipTransition_Canceled(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
+ }
}
@Override
@@ -345,20 +440,29 @@
if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) {
setState(STATE_PIP);
}
- if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
+ }
}
private void setState(@State int state) {
if (DEBUG) {
- Log.d(TAG, "setState(), state=" + stateToName(state) + ", prev="
- + stateToName(mState));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setState(), state=%s, prev=%s",
+ TAG, stateToName(state), stateToName(mState));
}
mState = state;
if (mState == STATE_PIP_MENU) {
- if (DEBUG) Log.d(TAG, " > show menu");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > show menu", TAG);
+ }
mTvPipMenuController.showMenu();
}
+
+ updatePinnedStackBounds();
}
private void loadConfigurations() {
@@ -366,12 +470,6 @@
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
}
- private DisplayInfo getDisplayInfo() {
- final DisplayInfo displayInfo = new DisplayInfo();
- mContext.getDisplay().getDisplayInfo(displayInfo);
- return displayInfo;
- }
-
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
@@ -388,7 +486,10 @@
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (task.getWindowingMode() == WINDOWING_MODE_PINNED) {
- if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPinnedActivityRestartAttempt()", TAG);
+ }
// If the "Pip-ed" Activity is launched again by Launcher or intent, make it
// fullscreen.
@@ -404,8 +505,9 @@
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (DEBUG) {
- Log.d(TAG, "onImeVisibilityChanged(), visible=" + imeVisible
- + ", height=" + imeHeight);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onImeVisibilityChanged(), visible=%b, height=%d",
+ TAG, imeVisible, imeHeight);
}
if (imeVisible == mTvPipBoundsState.isImeShowing()
@@ -417,62 +519,72 @@
mTvPipBoundsState.setImeVisibility(imeVisible, imeHeight);
if (mState != STATE_NO_PIP) {
- movePinnedStack();
+ updatePinnedStackBounds();
}
}
@Override
public void onAspectRatioChanged(float ratio) {
- if (DEBUG) Log.d(TAG, "onAspectRatioChanged: " + ratio);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onAspectRatioChanged: %f", TAG, ratio);
+ }
boolean ratioChanged = mTvPipBoundsState.getAspectRatio() != ratio;
mTvPipBoundsState.setAspectRatio(ratio);
if (!mTvPipBoundsState.isTvPipExpanded() && ratioChanged) {
- movePinnedStack();
+ updatePinnedStackBounds();
}
}
@Override
public void onExpandedAspectRatioChanged(float ratio) {
- if (DEBUG) Log.d(TAG, "onExpandedAspectRatioChanged: " + ratio);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
+ }
// 0) No update to the ratio --> don't do anything
- if (mTvPipBoundsState.getTvExpandedAspectRatio() == ratio) {
+
+ if (Math.abs(mTvPipBoundsState.getDesiredTvExpandedAspectRatio() - ratio)
+ < EPS) {
return;
}
- mTvPipBoundsState.setTvExpandedAspectRatio(ratio, false);
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
// 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
// --> update bounds, but don't toggle
if (mTvPipBoundsState.isTvPipExpanded() && ratio != 0) {
- movePinnedStack();
+ mTvPipBoundsAlgorithm.updateExpandedPipSize();
+ updatePinnedStackBounds();
}
// 2) PiP is expanded, but expanded PiP was disabled
// --> collapse PiP
if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
int saveGravity = mTvPipBoundsAlgorithm
- .updatePositionOnExpandToggled(mPreviousGravity, false);
+ .updateGravityOnExpandToggled(mPreviousGravity, false);
if (saveGravity != Gravity.NO_GRAVITY) {
mPreviousGravity = saveGravity;
}
mTvPipBoundsState.setTvPipExpanded(false);
- movePinnedStack();
+ updatePinnedStackBounds();
}
// 3) PiP not expanded and not manually collapsed and expand was enabled
// --> expand to new ratio
if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
+ mTvPipBoundsAlgorithm.updateExpandedPipSize();
int saveGravity = mTvPipBoundsAlgorithm
- .updatePositionOnExpandToggled(mPreviousGravity, true);
+ .updateGravityOnExpandToggled(mPreviousGravity, true);
if (saveGravity != Gravity.NO_GRAVITY) {
mPreviousGravity = saveGravity;
}
mTvPipBoundsState.setTvPipExpanded(true);
- movePinnedStack();
+ updatePinnedStackBounds();
}
}
@@ -481,35 +593,50 @@
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "onActionsChanged()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onActionsChanged()", TAG);
+ }
mTvPipMenuController.setAppActions(actions);
}
});
} catch (RemoteException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to register pinned stack listener, %s", TAG, e);
}
}
private static TaskInfo getPinnedTaskInfo() {
- if (DEBUG) Log.d(TAG, "getPinnedTaskInfo()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getPinnedTaskInfo()", TAG);
+ }
try {
final TaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (DEBUG) Log.d(TAG, " > taskInfo=" + taskInfo);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > taskInfo=%s", TAG, taskInfo);
+ }
return taskInfo;
} catch (RemoteException e) {
- Log.e(TAG, "getRootTaskInfo() failed", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getRootTaskInfo() failed, %s", TAG, e);
return null;
}
}
private static void removeTask(int taskId) {
- if (DEBUG) Log.d(TAG, "removeTask(), taskId=" + taskId);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removeTask(), taskId=%d", TAG, taskId);
+ }
try {
ActivityTaskManager.getService().removeTask(taskId);
} catch (Exception e) {
- Log.e(TAG, "Atm.removeTask() failed", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Atm.removeTask() failed, %s", TAG, e);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
new file mode 100644
index 0000000..5ac7a72
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -0,0 +1,741 @@
+/*
+ * 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.wm.shell.pip.tv
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.Size
+import android.view.Gravity
+import com.android.wm.shell.pip.PipBoundsState
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+private const val DEFAULT_PIP_MARGINS = 48
+private const val DEFAULT_STASH_DURATION = 5000L
+private const val RELAX_DEPTH = 1
+private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15
+
+/**
+ * This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking
+ * into account app defined keep clear areas.
+ *
+ * @param clock A function returning a current timestamp (in milliseconds)
+ */
+class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
+ /**
+ * Result of the positioning algorithm.
+ *
+ * @param bounds The bounds the PiP should be placed at
+ * @param anchorBounds The bounds of the PiP anchor position
+ * (where the PiP would be placed if there were no keep clear areas)
+ * @param stashType Where the PiP has been stashed, if at all
+ * @param unstashDestinationBounds If stashed, the PiP should move to this position after
+ * [stashDuration] has passed.
+ * @param unstashTime If stashed, the time at which the PiP should move
+ * to [unstashDestinationBounds]
+ */
+ data class Placement(
+ val bounds: Rect,
+ val anchorBounds: Rect,
+ @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
+ val unstashDestinationBounds: Rect? = null,
+ val unstashTime: Long = 0L
+ ) {
+ /** Bounds to use if the PiP should not be stashed. */
+ fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
+ }
+
+ /** The size of the screen */
+ private var screenSize = Size(0, 0)
+
+ /** The bounds the PiP is allowed to move in */
+ private var movementBounds = Rect()
+
+ /** Padding to add between a keep clear area that caused the PiP to move and the PiP */
+ var pipAreaPadding = DEFAULT_PIP_MARGINS
+
+ /** The distance the PiP peeks into the screen when stashed */
+ var stashOffset = DEFAULT_PIP_MARGINS
+
+ /**
+ * How long (in milliseconds) the PiP should stay stashed for after the last time the
+ * keep clear areas causing the PiP to stash have changed.
+ */
+ var stashDuration = DEFAULT_STASH_DURATION
+
+ /** The fraction of screen width/height restricted keep clear areas can move the PiP */
+ var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION
+
+ private var pipGravity = Gravity.BOTTOM or Gravity.RIGHT
+ private var transformedScreenBounds = Rect()
+ private var transformedMovementBounds = Rect()
+
+ private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
+ private var lastStashTime: Long = Long.MIN_VALUE
+
+ /**
+ * Calculates the position the PiP should be placed at, taking into consideration the
+ * given keep clear areas.
+ *
+ * Restricted keep clear areas can move the PiP only by a limited amount, and may be ignored
+ * if there is no space for the PiP to move to.
+ * Apps holding the permission [android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS]
+ * can declare unrestricted keep clear areas, which can move the PiP farther and placement will
+ * always try to respect these areas.
+ *
+ * If no free space the PiP is allowed to move to can be found, a stashed position is returned
+ * as [Placement.bounds], along with a position to move to once [Placement.unstashTime] has
+ * passed as [Placement.unstashDestinationBounds].
+ *
+ * @param pipSize The size of the PiP window
+ * @param restrictedAreas The restricted keep clear areas
+ * @param unrestrictedAreas The unrestricted keep clear areas
+ *
+ */
+ fun calculatePipPosition(
+ pipSize: Size,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Placement {
+ val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
+ val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
+ val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
+
+ val result = calculatePipPositionTransformed(
+ pipAnchorBounds,
+ transformedRestrictedAreas,
+ transformedUnrestrictedAreas
+ )
+
+ val screenSpaceBounds = fromTransformedSpace(result.bounds)
+ return Placement(
+ screenSpaceBounds,
+ fromTransformedSpace(result.anchorBounds),
+ getStashType(screenSpaceBounds, movementBounds),
+ result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
+ result.unstashTime
+ )
+ }
+
+ /**
+ * Filters out areas that encompass the entire movement bounds and returns them mapped to
+ * the base case space.
+ *
+ * Areas encompassing the entire movement bounds can occur when a full-screen View gets focused,
+ * but we don't want this to cause the PiP to get stashed.
+ */
+ private fun transformAndFilterAreas(areas: Set<Rect>): Set<Rect> {
+ return areas.mapNotNullTo(mutableSetOf()) {
+ when {
+ it.contains(movementBounds) -> null
+ else -> toTransformedSpace(it)
+ }
+ }
+ }
+
+ /**
+ * Calculates the position the PiP should be placed at, taking into consideration the
+ * given keep clear areas.
+ * All parameters are transformed from screen space to the base case space, where the PiP
+ * anchor is in the bottom right corner / on the right side.
+ *
+ * @see [calculatePipPosition]
+ */
+ private fun calculatePipPositionTransformed(
+ pipAnchorBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Placement {
+ if (restrictedAreas.isEmpty() && unrestrictedAreas.isEmpty()) {
+ return Placement(pipAnchorBounds, pipAnchorBounds)
+ }
+
+ // First try to find a free position to move to
+ val freeMovePos = findFreeMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas)
+ if (freeMovePos != null) {
+ lastAreasOverlappingUnstashPosition = emptySet()
+ return Placement(freeMovePos, pipAnchorBounds)
+ }
+
+ // If no free position is found, we have to stash the PiP.
+ // Find the position the PiP should return to once it unstashes by doing a relaxed
+ // search, or ignoring restricted areas, or returning to the anchor position
+ val unstashBounds =
+ findRelaxedMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas)
+ ?: findFreeMovePosition(pipAnchorBounds, emptySet(), unrestrictedAreas)
+ ?: pipAnchorBounds
+
+ val keepClearAreas = restrictedAreas + unrestrictedAreas
+ val areasOverlappingUnstashPosition =
+ keepClearAreas.filter { Rect.intersects(it, unstashBounds) }.toSet()
+ val areasOverlappingUnstashPositionChanged =
+ !lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition)
+ lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition
+
+ val now = clock()
+ if (areasOverlappingUnstashPositionChanged) {
+ lastStashTime = now
+ }
+
+ // If overlapping areas haven't changed and the stash duration has passed, we can
+ // place the PiP at the unstash position
+ val unstashTime = lastStashTime + stashDuration
+ if (now >= unstashTime) {
+ return Placement(unstashBounds, pipAnchorBounds)
+ }
+
+ // Otherwise, we'll stash it close to the unstash position
+ val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas)
+ return Placement(
+ stashedBounds,
+ pipAnchorBounds,
+ getStashType(stashedBounds, transformedMovementBounds),
+ unstashBounds,
+ unstashTime
+ )
+ }
+
+ @PipBoundsState.StashType
+ private fun getStashType(stashedBounds: Rect, movementBounds: Rect): Int {
+ return when {
+ stashedBounds.left < movementBounds.left -> STASH_TYPE_LEFT
+ stashedBounds.right > movementBounds.right -> STASH_TYPE_RIGHT
+ stashedBounds.top < movementBounds.top -> STASH_TYPE_TOP
+ stashedBounds.bottom > movementBounds.bottom -> STASH_TYPE_BOTTOM
+ else -> STASH_TYPE_NONE
+ }
+ }
+
+ private fun findRelaxedMovePosition(
+ pipAnchorBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Rect? {
+ if (RELAX_DEPTH <= 0) {
+ // relaxed search disabled
+ return null
+ }
+
+ return findRelaxedMovePosition(
+ RELAX_DEPTH,
+ pipAnchorBounds,
+ restrictedAreas.toMutableSet(),
+ unrestrictedAreas
+ )
+ }
+
+ private fun findRelaxedMovePosition(
+ depth: Int,
+ pipAnchorBounds: Rect,
+ restrictedAreas: MutableSet<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Rect? {
+ if (depth == 0) {
+ return findFreeMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas)
+ }
+
+ val candidates = mutableListOf<Rect>()
+ val areasToExclude = restrictedAreas.toList()
+ for (area in areasToExclude) {
+ restrictedAreas.remove(area)
+ val candidate = findRelaxedMovePosition(
+ depth - 1,
+ pipAnchorBounds,
+ restrictedAreas,
+ unrestrictedAreas
+ )
+ restrictedAreas.add(area)
+
+ if (candidate != null) {
+ candidates.add(candidate)
+ }
+ }
+ return candidates.minByOrNull { candidateCost(it, pipAnchorBounds) }
+ }
+
+ /** Cost function to evaluate candidate bounds */
+ private fun candidateCost(candidateBounds: Rect, pipAnchorBounds: Rect): Int {
+ // squared euclidean distance of corresponding rect corners
+ val dx = candidateBounds.left - pipAnchorBounds.left
+ val dy = candidateBounds.top - pipAnchorBounds.top
+ return dx * dx + dy * dy
+ }
+
+ private fun findFreeMovePosition(
+ pipAnchorBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): Rect? {
+ val movementBounds = transformedMovementBounds
+ val candidateEdgeRects = mutableListOf<Rect>()
+ val minRestrictedLeft =
+ pipAnchorBounds.right - screenSize.width * maxRestrictedDistanceFraction
+
+ candidateEdgeRects.add(
+ movementBounds.offsetCopy(movementBounds.width() + pipAreaPadding, 0)
+ )
+ candidateEdgeRects.addAll(unrestrictedAreas)
+ candidateEdgeRects.addAll(restrictedAreas.filter { it.left >= minRestrictedLeft })
+
+ // throw out edges that are too close to the left screen edge to fit the PiP
+ val minLeft = movementBounds.left + pipAnchorBounds.width()
+ candidateEdgeRects.retainAll { it.left - pipAreaPadding > minLeft }
+ candidateEdgeRects.sortBy { -it.left }
+
+ val maxRestrictedDY = (screenSize.height * maxRestrictedDistanceFraction).roundToInt()
+
+ val candidateBounds = mutableListOf<Rect>()
+ for (edgeRect in candidateEdgeRects) {
+ val edge = edgeRect.left - pipAreaPadding
+ val dx = (edge - pipAnchorBounds.width()) - pipAnchorBounds.left
+ val candidatePipBounds = pipAnchorBounds.offsetCopy(dx, 0)
+ val searchUp = true
+ val searchDown = !isPipAnchoredToCorner()
+
+ if (searchUp) {
+ val event = findMinMoveUp(candidatePipBounds, restrictedAreas, unrestrictedAreas)
+ val padding = if (event.start) 0 else pipAreaPadding
+ val dy = event.pos - pipAnchorBounds.bottom - padding
+ val maxDY = if (event.unrestricted) movementBounds.height() else maxRestrictedDY
+ val candidate = pipAnchorBounds.offsetCopy(dx, dy)
+ val isOnScreen = candidate.top > movementBounds.top
+ val hangingMidAir = !candidate.intersectsY(edgeRect)
+ if (isOnScreen && abs(dy) <= maxDY && !hangingMidAir) {
+ candidateBounds.add(candidate)
+ }
+ }
+
+ if (searchDown) {
+ val event = findMinMoveDown(candidatePipBounds, restrictedAreas, unrestrictedAreas)
+ val padding = if (event.start) 0 else pipAreaPadding
+ val dy = event.pos - pipAnchorBounds.top + padding
+ val maxDY = if (event.unrestricted) movementBounds.height() else maxRestrictedDY
+ val candidate = pipAnchorBounds.offsetCopy(dx, dy)
+ val isOnScreen = candidate.bottom < movementBounds.bottom
+ val hangingMidAir = !candidate.intersectsY(edgeRect)
+ if (isOnScreen && abs(dy) <= maxDY && !hangingMidAir) {
+ candidateBounds.add(candidate)
+ }
+ }
+ }
+
+ candidateBounds.sortBy { candidateCost(it, pipAnchorBounds) }
+ return candidateBounds.firstOrNull()
+ }
+
+ private fun getNearbyStashedPosition(bounds: Rect, keepClearAreas: Set<Rect>): Rect {
+ val screenBounds = transformedScreenBounds
+ val stashCandidates = Array(2) { Rect(bounds) }
+ val areasOverlappingPipX = keepClearAreas.filter { it.intersectsX(bounds) }
+ val areasOverlappingPipY = keepClearAreas.filter { it.intersectsY(bounds) }
+
+ if (screenBounds.bottom - bounds.bottom <= bounds.top - screenBounds.top) {
+ // bottom is closer than top, stash downwards
+ val fullStashTop = screenBounds.bottom - stashOffset
+
+ val maxBottom = areasOverlappingPipX.maxByOrNull { it.bottom }!!.bottom
+ val partialStashTop = maxBottom + pipAreaPadding
+
+ val downPosition = stashCandidates[0]
+ downPosition.offsetTo(bounds.left, min(fullStashTop, partialStashTop))
+ } else {
+ // top is closer than bottom, stash upwards
+ val fullStashY = screenBounds.top - bounds.height() + stashOffset
+
+ val minTop = areasOverlappingPipX.minByOrNull { it.top }!!.top
+ val partialStashY = minTop - bounds.height() - pipAreaPadding
+
+ val upPosition = stashCandidates[0]
+ upPosition.offsetTo(bounds.left, max(fullStashY, partialStashY))
+ }
+
+ if (screenBounds.right - bounds.right <= bounds.left - screenBounds.left) {
+ // right is closer than left, stash rightwards
+ val fullStashLeft = screenBounds.right - stashOffset
+
+ val maxRight = areasOverlappingPipY.maxByOrNull { it.right }!!.right
+ val partialStashLeft = maxRight + pipAreaPadding
+
+ val rightPosition = stashCandidates[1]
+ rightPosition.offsetTo(min(fullStashLeft, partialStashLeft), bounds.top)
+ } else {
+ // left is closer than right, stash leftwards
+ val fullStashLeft = screenBounds.left - bounds.width() + stashOffset
+
+ val minLeft = areasOverlappingPipY.minByOrNull { it.left }!!.left
+ val partialStashLeft = minLeft - bounds.width() - pipAreaPadding
+
+ val rightPosition = stashCandidates[1]
+ rightPosition.offsetTo(max(fullStashLeft, partialStashLeft), bounds.top)
+ }
+
+ return stashCandidates.minByOrNull {
+ val dx = abs(it.left - bounds.left)
+ val dy = abs(it.top - bounds.top)
+ dx * bounds.height() + dy * bounds.width()
+ }!!
+ }
+
+ /**
+ * Prevents the PiP from being stashed for the current set of keep clear areas.
+ * The PiP may stash again if keep clear areas change.
+ */
+ fun keepUnstashedForCurrentKeepClearAreas() {
+ lastStashTime = Long.MIN_VALUE
+ }
+
+ /**
+ * Updates the size of the screen.
+ *
+ * @param size The new size of the screen
+ */
+ fun setScreenSize(size: Size) {
+ if (screenSize == size) {
+ return
+ }
+
+ screenSize = size
+ transformedScreenBounds =
+ toTransformedSpace(Rect(0, 0, screenSize.width, screenSize.height))
+ transformedMovementBounds = toTransformedSpace(transformedMovementBounds)
+ }
+
+ /**
+ * Updates the bounds within which the PiP is allowed to move.
+ *
+ * @param bounds The new movement bounds
+ */
+ fun setMovementBounds(bounds: Rect) {
+ if (movementBounds == bounds) {
+ return
+ }
+
+ movementBounds.set(bounds)
+ transformedMovementBounds = toTransformedSpace(movementBounds)
+ }
+
+ /**
+ * Sets the corner/side of the PiP's home position.
+ */
+ fun setGravity(gravity: Int) {
+ if (pipGravity == gravity) return
+
+ pipGravity = gravity
+ transformedScreenBounds =
+ toTransformedSpace(Rect(0, 0, screenSize.width, screenSize.height))
+ transformedMovementBounds = toTransformedSpace(movementBounds)
+ }
+
+ /**
+ * @param open Whether this event marks the opening of an occupied segment
+ * @param pos The coordinate of this event
+ * @param unrestricted Whether this event was generated by an unrestricted keep clear area
+ * @param start Marks the special start event. Earlier events are skipped when sweeping
+ */
+ data class SweepLineEvent(
+ val open: Boolean,
+ val pos: Int,
+ val unrestricted: Boolean,
+ val start: Boolean = false
+ )
+
+ /**
+ * Returns a [SweepLineEvent] representing the minimal move up from [pipBounds] that clears
+ * the given keep clear areas.
+ */
+ private fun findMinMoveUp(
+ pipBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): SweepLineEvent {
+ val events = mutableListOf<SweepLineEvent>()
+ val generateEvents: (Boolean) -> (Rect) -> Unit = { unrestricted ->
+ { area ->
+ if (pipBounds.intersectsX(area)) {
+ events.add(SweepLineEvent(true, area.bottom, unrestricted))
+ events.add(SweepLineEvent(false, area.top, unrestricted))
+ }
+ }
+ }
+
+ restrictedAreas.forEach(generateEvents(false))
+ unrestrictedAreas.forEach(generateEvents(true))
+
+ return sweepLineFindEarliestGap(
+ events,
+ pipBounds.height() + pipAreaPadding,
+ pipBounds.bottom,
+ pipBounds.height()
+ )
+ }
+
+ /**
+ * Returns a [SweepLineEvent] representing the minimal move down from [pipBounds] that clears
+ * the given keep clear areas.
+ */
+ private fun findMinMoveDown(
+ pipBounds: Rect,
+ restrictedAreas: Set<Rect>,
+ unrestrictedAreas: Set<Rect>
+ ): SweepLineEvent {
+ val events = mutableListOf<SweepLineEvent>()
+ val generateEvents: (Boolean) -> (Rect) -> Unit = { unrestricted ->
+ { area ->
+ if (pipBounds.intersectsX(area)) {
+ events.add(SweepLineEvent(true, -area.top, unrestricted))
+ events.add(SweepLineEvent(false, -area.bottom, unrestricted))
+ }
+ }
+ }
+
+ restrictedAreas.forEach(generateEvents(false))
+ unrestrictedAreas.forEach(generateEvents(true))
+
+ val earliestEvent = sweepLineFindEarliestGap(
+ events,
+ pipBounds.height() + pipAreaPadding,
+ -pipBounds.top,
+ pipBounds.height()
+ )
+
+ return earliestEvent.copy(pos = -earliestEvent.pos)
+ }
+
+ /**
+ * Takes a list of events representing the starts & ends of occupied segments, and
+ * returns the earliest event whose position is unoccupied and has [gapSize] distance to the
+ * next event.
+ *
+ * @param events List of [SweepLineEvent] representing occupied segments
+ * @param gapSize Size of the gap to search for
+ * @param startPos The position to start the search on.
+ * Inserts a special event marked with [SweepLineEvent.start].
+ * @param startGapSize Used instead of [gapSize] for the start event
+ */
+ private fun sweepLineFindEarliestGap(
+ events: MutableList<SweepLineEvent>,
+ gapSize: Int,
+ startPos: Int,
+ startGapSize: Int
+ ): SweepLineEvent {
+ events.add(
+ SweepLineEvent(
+ open = false,
+ pos = startPos,
+ unrestricted = true,
+ start = true
+ )
+ )
+ events.sortBy { -it.pos }
+
+ // sweep
+ var openCount = 0
+ var i = 0
+ while (i < events.size) {
+ val event = events[i]
+ if (!event.start) {
+ if (event.open) {
+ openCount++
+ } else {
+ openCount--
+ }
+ }
+
+ if (openCount == 0) {
+ // check if placement is possible
+ val candidate = event.pos
+ if (candidate > startPos) {
+ i++
+ continue
+ }
+
+ val eventGapSize = if (event.start) startGapSize else gapSize
+ val nextEvent = events.getOrNull(i + 1)
+ if (nextEvent == null || nextEvent.pos < candidate - eventGapSize) {
+ return event
+ }
+ }
+ i++
+ }
+
+ return events.last()
+ }
+
+ private fun shouldTransformFlipX(): Boolean {
+ return when (pipGravity) {
+ (Gravity.TOP), (Gravity.TOP or Gravity.CENTER_HORIZONTAL) -> true
+ (Gravity.TOP or Gravity.LEFT) -> true
+ (Gravity.LEFT), (Gravity.LEFT or Gravity.CENTER_VERTICAL) -> true
+ (Gravity.BOTTOM or Gravity.LEFT) -> true
+ else -> false
+ }
+ }
+
+ private fun shouldTransformFlipY(): Boolean {
+ return when (pipGravity) {
+ (Gravity.TOP or Gravity.LEFT) -> true
+ (Gravity.TOP or Gravity.RIGHT) -> true
+ else -> false
+ }
+ }
+
+ private fun shouldTransformRotate(): Boolean {
+ val horizontalGravity = pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+ val leftOrRight = horizontalGravity == Gravity.LEFT || horizontalGravity == Gravity.RIGHT
+
+ if (leftOrRight) return false
+ return when (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) {
+ (Gravity.TOP) -> true
+ (Gravity.BOTTOM) -> true
+ else -> false
+ }
+ }
+
+ /**
+ * Transforms the given rect from screen space into the base case space, where the PiP
+ * anchor is positioned in the bottom right corner or on the right side (for expanded PiP).
+ *
+ * @see [fromTransformedSpace]
+ */
+ private fun toTransformedSpace(r: Rect): Rect {
+ var screenWidth = screenSize.width
+ var screenHeight = screenSize.height
+
+ val tl = Point(r.left, r.top)
+ val tr = Point(r.right, r.top)
+ val br = Point(r.right, r.bottom)
+ val bl = Point(r.left, r.bottom)
+ val corners = arrayOf(tl, tr, br, bl)
+
+ // rotate first (CW)
+ if (shouldTransformRotate()) {
+ corners.forEach { p ->
+ val px = p.x
+ val py = p.y
+ p.x = py
+ p.y = -px
+ p.y += screenWidth // shift back screen into positive quadrant
+ }
+ screenWidth = screenSize.height
+ screenHeight = screenSize.width
+ }
+
+ // flip second
+ corners.forEach {
+ if (shouldTransformFlipX()) it.x = screenWidth - it.x
+ if (shouldTransformFlipY()) it.y = screenHeight - it.y
+ }
+
+ val top = corners.minByOrNull { it.y }!!.y
+ val right = corners.maxByOrNull { it.x }!!.x
+ val bottom = corners.maxByOrNull { it.y }!!.y
+ val left = corners.minByOrNull { it.x }!!.x
+
+ return Rect(left, top, right, bottom)
+ }
+
+ /**
+ * Transforms the given rect from the base case space, where the PiP anchor is positioned in
+ * the bottom right corner or on the right side, back into screen space.
+ *
+ * @see [toTransformedSpace]
+ */
+ private fun fromTransformedSpace(r: Rect): Rect {
+ val rotate = shouldTransformRotate()
+ val transformedScreenWidth = if (rotate) screenSize.height else screenSize.width
+ val transformedScreenHeight = if (rotate) screenSize.width else screenSize.height
+
+ val tl = Point(r.left, r.top)
+ val tr = Point(r.right, r.top)
+ val br = Point(r.right, r.bottom)
+ val bl = Point(r.left, r.bottom)
+ val corners = arrayOf(tl, tr, br, bl)
+
+ // flip first
+ corners.forEach {
+ if (shouldTransformFlipX()) it.x = transformedScreenWidth - it.x
+ if (shouldTransformFlipY()) it.y = transformedScreenHeight - it.y
+ }
+
+ // rotate second (CCW)
+ if (rotate) {
+ corners.forEach { p ->
+ p.y -= screenSize.width // undo shift back screen into positive quadrant
+ val px = p.x
+ val py = p.y
+ p.x = -py
+ p.y = px
+ }
+ }
+
+ val top = corners.minByOrNull { it.y }!!.y
+ val right = corners.maxByOrNull { it.x }!!.x
+ val bottom = corners.maxByOrNull { it.y }!!.y
+ val left = corners.minByOrNull { it.x }!!.x
+
+ return Rect(left, top, right, bottom)
+ }
+
+ /** PiP anchor bounds in base case for given gravity */
+ private fun getNormalPipAnchorBounds(pipSize: Size, movementBounds: Rect): Rect {
+ var size = pipSize
+ val rotateCW = shouldTransformRotate()
+ if (rotateCW) {
+ size = Size(pipSize.height, pipSize.width)
+ }
+
+ val pipBounds = Rect()
+ if (isPipAnchoredToCorner()) {
+ // bottom right
+ Gravity.apply(
+ Gravity.BOTTOM or Gravity.RIGHT,
+ size.width,
+ size.height,
+ movementBounds,
+ pipBounds
+ )
+ return pipBounds
+ } else {
+ // expanded, right side
+ Gravity.apply(Gravity.RIGHT, size.width, size.height, movementBounds, pipBounds)
+ return pipBounds
+ }
+ }
+
+ private fun isPipAnchoredToCorner(): Boolean {
+ val left = (pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
+ val right = (pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.RIGHT
+ val top = (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP
+ val bottom = (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM
+
+ val horizontal = left || right
+ val vertical = top || bottom
+
+ return horizontal && vertical
+ }
+
+ private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
+ private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
+ private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 32ebe2d..1a035c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -30,17 +30,18 @@
import android.graphics.RectF;
import android.os.Handler;
import android.os.RemoteException;
-import android.util.Log;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.List;
@@ -110,7 +111,10 @@
}
void setDelegate(Delegate delegate) {
- if (DEBUG) Log.d(TAG, "setDelegate(), delegate=" + delegate);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setDelegate(), delegate=%s", TAG, delegate);
+ }
if (mDelegate != null) {
throw new IllegalStateException(
"The delegate has already been set and should not change.");
@@ -133,7 +137,10 @@
}
private void attachPipMenuView() {
- if (DEBUG) Log.d(TAG, "attachPipMenuView()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: attachPipMenuView()", TAG);
+ }
if (mPipMenuView != null) {
detachPipMenuView();
@@ -148,7 +155,10 @@
@Override
public void showMenu() {
- if (DEBUG) Log.d(TAG, "showMenu()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenu()", TAG);
+ }
if (mPipMenuView != null) {
Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
@@ -174,8 +184,8 @@
}
void updateExpansionState() {
- mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipEnabled()
- && mTvPipBoundsState.getTvExpandedAspectRatio() != 0);
+ mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
}
@@ -189,10 +199,16 @@
void hideMenu() {
if (!isMenuVisible()) {
- if (DEBUG) Log.d(TAG, "hideMenu() - Menu isn't visible, so don't hide");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMenu() - Menu isn't visible, so don't hide", TAG);
+ }
return;
} else {
- if (DEBUG) Log.d(TAG, "hideMenu()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMenu()", TAG);
+ }
}
mPipMenuView.hide();
@@ -202,21 +218,33 @@
}
}
+ boolean isInMoveMode() {
+ return mInMoveMode;
+ }
+
@Override
public void onEnterMoveMode() {
- if (DEBUG) Log.d(TAG, "onEnterMoveMode - " + mInMoveMode);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onEnterMoveMode - %b", TAG, mInMoveMode);
+ }
mInMoveMode = true;
mPipMenuView.showMenuButtons(false);
mPipMenuView.showMovementHints(mDelegate.getPipGravity());
+ mDelegate.onInMoveModeChanged();
}
@Override
public boolean onExitMoveMode() {
- if (DEBUG) Log.d(TAG, "onExitMoveMode - " + mInMoveMode);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExitMoveMode - %b", TAG, mInMoveMode);
+ }
if (mInMoveMode) {
mInMoveMode = false;
mPipMenuView.showMenuButtons(true);
mPipMenuView.hideMovementHints();
+ mDelegate.onInMoveModeChanged();
return true;
}
return false;
@@ -224,7 +252,10 @@
@Override
public boolean onPipMovement(int keycode) {
- if (DEBUG) Log.d(TAG, "onPipMovement - " + mInMoveMode);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipMovement - %b", TAG, mInMoveMode);
+ }
if (mInMoveMode) {
mDelegate.movePip(keycode);
}
@@ -240,12 +271,18 @@
@Override
public void setAppActions(ParceledListSlice<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "setAppActions()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setAppActions()", TAG);
+ }
updateAdditionalActionsList(mAppActions, actions.getList());
}
private void onMediaActionsChanged(List<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "onMediaActionsChanged()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMediaActionsChanged()", TAG);
+ }
// Hide disabled actions.
List<RemoteAction> enabledActions = new ArrayList<>();
@@ -286,7 +323,10 @@
@Override
public boolean isMenuVisible() {
boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
- if (DEBUG) Log.d(TAG, "isMenuVisible: " + isVisible);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: isMenuVisible: %b", TAG, isVisible);
+ }
return isVisible;
}
@@ -297,7 +337,10 @@
public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
@android.annotation.Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
- if (DEBUG) Log.d(TAG, "resizePipMenu: " + destinationBounds.toShortString());
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipMenu: %s", TAG, destinationBounds.toShortString());
+ }
if (destinationBounds.isEmpty()) {
return;
}
@@ -329,10 +372,16 @@
@Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
Rect pipDestBounds) {
- if (DEBUG) Log.d(TAG, "movePipMenu: " + pipDestBounds.toShortString());
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipMenu: %s", TAG, pipDestBounds.toShortString());
+ }
if (pipDestBounds.isEmpty()) {
- if (transaction == null && DEBUG) Log.d(TAG, "no transaction given");
+ if (transaction == null && DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: no transaction given", TAG);
+ }
return;
}
if (!maybeCreateSyncApplier()) {
@@ -345,10 +394,16 @@
// resizing and the PiP menu is also resized. We then want to do a scale from the current
// new menu bounds.
if (pipLeash != null && transaction != null) {
- if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
+ }
mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
} else {
- if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on menu width and height");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: mTmpSourceBounds based on menu width and height", TAG);
+ }
mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
}
@@ -383,7 +438,8 @@
private boolean maybeCreateSyncApplier() {
if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
- Log.v(TAG, "Not going to move PiP, either menu or its parent is not created.");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
return false;
}
@@ -406,7 +462,10 @@
@Override
public void updateMenuBounds(Rect destinationBounds) {
Rect menuBounds = getMenuBounds(destinationBounds);
- if (DEBUG) Log.d(TAG, "updateMenuBounds: " + menuBounds.toShortString());
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
+ }
mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
@@ -417,7 +476,7 @@
@Override
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- Log.d(TAG, "onFocusTaskChanged");
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onFocusTaskChanged", TAG);
}
@Override
@@ -447,6 +506,8 @@
void movePip(int keycode);
+ void onInMoveModeChanged();
+
int getPipGravity();
void togglePipExpansion();
@@ -457,13 +518,17 @@
}
private void grantPipMenuFocus(boolean grantFocus) {
- if (DEBUG) Log.d(TAG, "grantWindowFocus(" + grantFocus + ")");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: grantWindowFocus(%b)", TAG, grantFocus);
+ }
try {
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
} catch (Exception e) {
- Log.e(TAG, "Unable to update focus", e);
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus, %s", TAG, e);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 3090139..984dea2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -31,7 +31,6 @@
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.SurfaceControl;
@@ -45,7 +44,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.List;
@@ -118,17 +119,24 @@
}
void updateLayout(Rect updatedBounds) {
- Log.d(TAG, "update menu layout: " + updatedBounds.toShortString());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: update menu layout: %s", TAG, updatedBounds.toShortString());
boolean previouslyVertical =
mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
boolean vertical = updatedBounds.height() > updatedBounds.width();
mCurrentBounds = updatedBounds;
if (previouslyVertical == vertical) {
- if (DEBUG) Log.d(TAG, "no update for menu layout");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: no update for menu layout", TAG);
+ }
return;
} else {
- if (DEBUG) Log.d(TAG, "change menu layout to vertical: " + vertical);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: change menu layout to vertical: %b", TAG, vertical);
+ }
}
if (vertical) {
@@ -154,7 +162,10 @@
}
void setIsExpanded(boolean expanded) {
- if (DEBUG) Log.d(TAG, "setIsExpanded, expanded: " + expanded);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setIsExpanded, expanded: %b", TAG, expanded);
+ }
mExpandButton.setImageResource(
expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
mExpandButton.setTextAndDescription(
@@ -162,7 +173,10 @@
}
void show(boolean inMoveMode, int gravity) {
- if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: show(), inMoveMode: %b", TAG, inMoveMode);
+ }
if (inMoveMode) {
showMovementHints(gravity);
} else {
@@ -172,7 +186,9 @@
}
void hide() {
- if (DEBUG) Log.d(TAG, "hide()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hide()", TAG);
+ }
animateAlphaTo(0, mActionButtonsContainer);
animateAlphaTo(0, mMenuFrameView);
hideMovementHints();
@@ -205,7 +221,10 @@
}
void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
- if (DEBUG) Log.d(TAG, "setAdditionalActions()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setAdditionalActions()", TAG);
+ }
// Make sure we exactly as many additional buttons as we have actions to display.
final int actionsNumber = actions.size();
@@ -278,10 +297,12 @@
try {
action.getActionIntent().send();
} catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Failed to send action", e);
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
}
} else {
- Log.w(TAG, "RemoteAction is null");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: RemoteAction is null", TAG);
}
}
}
@@ -289,8 +310,9 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (DEBUG) {
- Log.d(TAG, "dispatchKeyEvent, action: " + event.getAction()
- + ", keycode: " + event.getKeyCode());
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: dispatchKeyEvent, action: %d, keycode: %d",
+ TAG, event.getAction(), event.getKeyCode());
}
if (mListener != null && event.getAction() == ACTION_UP) {
switch (event.getKeyCode()) {
@@ -317,7 +339,10 @@
* Shows user hints for moving the PiP, e.g. arrows.
*/
public void showMovementHints(int gravity) {
- if (DEBUG) Log.d(TAG, "showMovementHints(), position: " + Gravity.toString(gravity));
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
+ }
animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
@@ -333,7 +358,10 @@
* Hides user hints for moving the PiP, e.g. arrows.
*/
public void hideMovementHints() {
- if (DEBUG) Log.d(TAG, "hideMovementHints()");
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMovementHints()", TAG);
+ }
animateAlphaTo(0, mArrowUp);
animateAlphaTo(0, mArrowRight);
animateAlphaTo(0, mArrowDown);
@@ -344,7 +372,10 @@
* Show or hide the pip user actions.
*/
public void showMenuButtons(boolean show) {
- if (DEBUG) Log.d(TAG, "showMenuButtons: " + show);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenuButtons: %b", TAG, show);
+ }
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index dd7e294..7bd3ce9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -28,13 +28,13 @@
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.os.Handler;
-import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.Log;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.Objects;
@@ -98,7 +98,10 @@
}
void setDelegate(Delegate delegate) {
- if (DEBUG) Log.d(TAG, "setDelegate(), delegate=" + delegate);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setDelegate(), delegate=%s", TAG, delegate);
+ }
if (mDelegate != null) {
throw new IllegalStateException(
"The delegate has already been set and should not change.");
@@ -240,7 +243,10 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "on(Broadcast)Receive(), action=" + action);
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+ }
if (ACTION_SHOW_PIP_MENU.equals(action)) {
mDelegate.showPictureInPictureMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9e53593..48dd1fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1329,8 +1329,11 @@
// Once the pending enter transition got merged, make sure to bring divider bar visible and
// clear the pending transition from cache to prevent mess-up the following state.
if (transition == mSplitTransitions.mPendingEnter) {
- finishEnterSplitScreen(null);
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ finishEnterSplitScreen(t);
mSplitTransitions.mPendingEnter = null;
+ t.apply();
+ mTransactionPool.release(t);
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 61e27f2..fb404b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.bubble
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
@@ -24,6 +25,8 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
import org.junit.runner.RunWith
import org.junit.Test
import org.junit.runners.Parameterized
@@ -69,9 +72,19 @@
}
}
- @FlakyTest
+ @Presubmit
@Test
fun testAppIsVisibleAtEnd() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ testSpec.assertLayersEnd {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ @FlakyTest
+ @Test
+ fun testAppIsVisibleAtEnd_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
testSpec.assertLayersEnd {
this.isVisible(testApp.component)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index a57d3e6..c43230e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.bubble
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -57,9 +58,19 @@
}
}
- @FlakyTest(bugId = 218642026)
+ @Presubmit
@Test
open fun testAppIsAlwaysVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ @FlakyTest(bugId = 218642026)
+ @Test
+ open fun testAppIsAlwaysVisible_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
testSpec.assertLayers {
this.isVisible(testApp.component)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
new file mode 100644
index 0000000..78903dc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.wm.shell.kidsmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.ForceShowNavigationBarSettingsObserver;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.startingsurface.StartingWindowController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KidsModeTaskOrganizerTest {
+ @Mock private ITaskOrganizerController mTaskOrganizerController;
+ @Mock private Context mContext;
+ @Mock private Handler mHandler;
+ @Mock private CompatUIController mCompatUI;
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private ShellExecutor mTestExecutor;
+ @Mock private DisplayController mDisplayController;
+ @Mock private SurfaceControl mLeash;
+ @Mock private WindowContainerToken mToken;
+ @Mock private WindowContainerTransaction mTransaction;
+ @Mock private ForceShowNavigationBarSettingsObserver mObserver;
+ @Mock private StartingWindowController mStartingWindowController;
+ @Mock private DisplayInsetsController mDisplayInsetsController;
+
+ KidsModeTaskOrganizer mOrganizer;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ try {
+ doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
+ .when(mTaskOrganizerController).registerTaskOrganizer(any());
+ } catch (RemoteException e) { }
+ mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
+ mHandler, mContext, mCompatUI, mSyncTransactionQueue, mDisplayController,
+ mDisplayInsetsController, Optional.empty(), mObserver));
+ mOrganizer.initialize(mStartingWindowController);
+ doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
+ doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testKidsModeOn() {
+ doReturn(true).when(mObserver).isEnabled();
+
+ mOrganizer.updateKidsModeState();
+
+ verify(mOrganizer, times(1)).enable();
+ verify(mOrganizer, times(1)).registerOrganizer();
+ verify(mOrganizer, times(1)).createRootTask(
+ eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
+
+ final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
+ WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
+ mOrganizer.onTaskAppeared(rootTask, mLeash);
+
+ assertThat(mOrganizer.mLaunchRootLeash).isEqualTo(mLeash);
+ assertThat(mOrganizer.mLaunchRootTask).isEqualTo(rootTask);
+ }
+
+ @Test
+ public void testKidsModeOff() {
+ doReturn(true).when(mObserver).isEnabled();
+ mOrganizer.updateKidsModeState();
+ final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
+ WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
+ mOrganizer.onTaskAppeared(rootTask, mLeash);
+
+ doReturn(false).when(mObserver).isEnabled();
+ mOrganizer.updateKidsModeState();
+
+
+ verify(mOrganizer, times(1)).disable();
+ verify(mOrganizer, times(1)).unregisterOrganizer();
+ verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
+ assertThat(mOrganizer.mLaunchRootLeash).isNull();
+ assertThat(mOrganizer.mLaunchRootTask).isNull();
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(
+ int taskId, int windowingMode, IBinder cookies) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.token = mToken;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ final ArrayList<IBinder> launchCookies = new ArrayList<>();
+ if (cookies != null) {
+ launchCookies.add(cookies);
+ }
+ taskInfo.launchCookies = launchCookies;
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
new file mode 100644
index 0000000..e6ba70e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -0,0 +1,469 @@
+/*
+ * 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.wm.shell.pip.tv
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.util.Size
+import android.view.Gravity
+import org.junit.runner.RunWith
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
+import org.junit.Before
+import org.junit.Test
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNull
+
+@RunWith(AndroidTestingRunner::class)
+class TvPipKeepClearAlgorithmTest {
+ private val DEFAULT_PIP_SIZE = Size(384, 216)
+ private val EXPANDED_WIDE_PIP_SIZE = Size(384*2, 216)
+ private val DASHBOARD_WIDTH = 484
+ private val BOTTOM_SHEET_HEIGHT = 524
+ private val STASH_OFFSET = 64
+ private val PADDING = 16
+ private val SCREEN_SIZE = Size(1920, 1080)
+ private val SCREEN_EDGE_INSET = 50
+
+ private lateinit var pipSize: Size
+ private lateinit var movementBounds: Rect
+ private lateinit var algorithm: TvPipKeepClearAlgorithm
+ private var currentTime = 0L
+ private var restrictedAreas = mutableSetOf<Rect>()
+ private var unrestrictedAreas = mutableSetOf<Rect>()
+ private var gravity: Int = 0
+
+ @Before
+ fun setup() {
+ movementBounds = Rect(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height)
+ movementBounds.inset(SCREEN_EDGE_INSET, SCREEN_EDGE_INSET)
+
+ restrictedAreas.clear()
+ unrestrictedAreas.clear()
+ currentTime = 0L
+ pipSize = DEFAULT_PIP_SIZE
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ algorithm = TvPipKeepClearAlgorithm({ currentTime })
+ algorithm.setScreenSize(SCREEN_SIZE)
+ algorithm.setMovementBounds(movementBounds)
+ algorithm.pipAreaPadding = PADDING
+ algorithm.stashOffset = STASH_OFFSET
+ algorithm.stashDuration = 5000L
+ algorithm.setGravity(gravity)
+ algorithm.maxRestrictedDistanceFraction = 0.3
+ }
+
+ @Test
+ fun testAnchorPosition_BottomRight() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_TopRight() {
+ gravity = Gravity.TOP or Gravity.RIGHT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_TopLeft() {
+ gravity = Gravity.TOP or Gravity.LEFT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_BottomLeft() {
+ gravity = Gravity.BOTTOM or Gravity.LEFT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Right() {
+ gravity = Gravity.RIGHT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Left() {
+ gravity = Gravity.LEFT
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Top() {
+ gravity = Gravity.TOP
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_Bottom() {
+ gravity = Gravity.BOTTOM
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_TopCenterHorizontal() {
+ gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_BottomCenterHorizontal() {
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_RightCenterVertical() {
+ gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
+ testAnchorPosition()
+ }
+
+ @Test
+ fun testAnchorPosition_LeftCenterVertical() {
+ gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
+ testAnchorPosition()
+ }
+
+ fun testAnchorPosition() {
+ val placement = getActualPlacement()
+
+ assertEquals(getExpectedAnchorBounds(), placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_KeepClearNotObstructing_StayAtAnchor() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.LEFT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = getExpectedAnchorBounds()
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedRightSidebar_PushedLeft() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorTopRight_UnrestrictedRightSidebar_PushedLeft() {
+ gravity = Gravity.TOP or Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomLeft_UnrestrictedRightSidebar_StayAtAnchor() {
+ gravity = Gravity.BOTTOM or Gravity.LEFT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() {
+ gravity = Gravity.BOTTOM
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun testExpanded_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() {
+ pipSize = EXPANDED_WIDE_PIP_SIZE
+ gravity = Gravity.BOTTOM
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ unrestrictedAreas.add(sidebar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0, 0)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_RestrictedSmallBottomBar_PushedUp() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(96)
+ restrictedAreas.add(bottomBar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0,
+ SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_RestrictedBottomSheet_StashDownAtAnchor() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ restrictedAreas.add(bottomBar)
+
+ val expectedBounds = getExpectedAnchorBounds()
+ expectedBounds.offsetTo(expectedBounds.left, SCREEN_SIZE.height - STASH_OFFSET)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_BOTTOM, placement.stashType)
+ assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedBottomSheet_PushUp() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val expectedBounds = anchorBoundsOffsetBy(0,
+ SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedBottomSheet_RestrictedSidebar_StashAboveBottomSheet() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+ }
+
+ @Test
+ fun test_AnchorBottomRight_UnrestrictedBottomSheet_UnrestrictedSidebar_PushUpLeft() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ unrestrictedAreas.add(sideBar)
+
+ val expectedBounds = anchorBoundsOffsetBy(
+ SCREEN_EDGE_INSET - sideBar.width() - PADDING,
+ SCREEN_EDGE_INSET - bottomBar.height() - PADDING
+ )
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_Stashed_UnstashBoundsBecomeUnobstructed_Unstashes() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ var placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+
+ currentTime += 1000
+
+ restrictedAreas.remove(sideBar)
+ placement = getActualPlacement()
+ assertEquals(expectedUnstashBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ var placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+
+ currentTime += algorithm.stashDuration
+
+ placement = getActualPlacement()
+ assertEquals(expectedUnstashBounds, placement.bounds)
+ assertNotStashed(placement)
+ }
+
+ @Test
+ fun test_Stashed_UnstashBoundsObstructionChanges_UnstashTimeExtended() {
+ gravity = Gravity.BOTTOM or Gravity.RIGHT
+
+ val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
+ unrestrictedAreas.add(bottomBar)
+
+ val maxRestrictedHorizontalPush =
+ (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt()
+ val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT)
+ restrictedAreas.add(sideBar)
+
+ val expectedUnstashBounds =
+ anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING)
+
+ val expectedBounds = Rect(expectedUnstashBounds)
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ var placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(algorithm.stashDuration, placement.unstashTime)
+
+ currentTime += 1000
+
+ val newObstruction = Rect(
+ 0,
+ expectedUnstashBounds.top,
+ expectedUnstashBounds.right,
+ expectedUnstashBounds.bottom
+ )
+ restrictedAreas.add(newObstruction)
+
+ placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
+ assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime)
+ }
+
+ private fun makeSideBar(width: Int, @Gravity.GravityFlags side: Int): Rect {
+ val sidebar = Rect(0, 0, width, SCREEN_SIZE.height)
+ if (side == Gravity.RIGHT) {
+ sidebar.offsetTo(SCREEN_SIZE.width - width, 0)
+ }
+ return sidebar
+ }
+
+ private fun makeBottomBar(height: Int): Rect {
+ return Rect(0, SCREEN_SIZE.height - height, SCREEN_SIZE.width, SCREEN_SIZE.height)
+ }
+
+ private fun getExpectedAnchorBounds(): Rect {
+ val expectedBounds = Rect()
+ Gravity.apply(gravity, pipSize.width, pipSize.height, movementBounds, expectedBounds)
+ return expectedBounds
+ }
+
+ private fun anchorBoundsOffsetBy(dx: Int, dy: Int): Rect {
+ val bounds = getExpectedAnchorBounds()
+ bounds.offset(dx, dy)
+ return bounds
+ }
+
+ private fun getActualPlacement(): Placement {
+ algorithm.setGravity(gravity)
+ return algorithm.calculatePipPosition(pipSize, restrictedAreas, unrestrictedAreas)
+ }
+
+ private fun assertNotStashed(actual: Placement) {
+ assertEquals(STASH_TYPE_NONE, actual.stashType)
+ assertNull(actual.unstashDestinationBounds)
+ assertEquals(0L, actual.unstashTime)
+ }
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 432196c..1a56b15 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6952,7 +6952,8 @@
for (Integer format : formatsList) {
int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
- codecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
+ codecConfigList.add(
+ new BluetoothCodecConfig.Builder().setCodecType(btSourceCodec).build());
}
}
return codecConfigList;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 47e402f..d8995b4 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1118,6 +1118,11 @@
private List<MediaRoute2Info> filterRoutesWithIndividualPreference(
List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) {
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ if (isSystemRouter()) {
+ // Individual discovery preferences do not apply for the system router.
+ filteredRoutes.addAll(routes);
+ return filteredRoutes;
+ }
for (MediaRoute2Info route : routes) {
if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
continue;
diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.java b/media/java/android/media/tv/interactive/AppLinkInfo.java
index cd201f7..0eb6fa8 100644
--- a/media/java/android/media/tv/interactive/AppLinkInfo.java
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,8 +26,7 @@
* App link information used by TV interactive app to launch Android apps.
*/
public final class AppLinkInfo implements Parcelable {
- private @NonNull String mPackageName;
- private @NonNull String mClassName;
+ private @NonNull ComponentName mComponentName;
private @Nullable String mUriScheme;
private @Nullable String mUriHost;
private @Nullable String mUriPrefix;
@@ -41,12 +41,11 @@
@Nullable String uriScheme,
@Nullable String uriHost,
@Nullable String uriPrefix) {
- this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mPackageName);
- this.mClassName = className;
+ NonNull.class, null, packageName);
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mClassName);
+ NonNull.class, null, className);
+ this.mComponentName = new ComponentName(packageName, className);
this.mUriScheme = uriScheme;
this.mUriHost = uriHost;
this.mUriPrefix = uriPrefix;
@@ -57,7 +56,7 @@
*/
@NonNull
public String getPackageName() {
- return mPackageName;
+ return mComponentName.getPackageName();
}
/**
@@ -65,7 +64,7 @@
*/
@NonNull
public String getClassName() {
- return mClassName;
+ return mComponentName.getClassName();
}
/**
@@ -95,8 +94,8 @@
@Override
public String toString() {
return "AppLinkInfo { "
- + "packageName = " + mPackageName + ", "
- + "className = " + mClassName + ", "
+ + "packageName = " + mComponentName.getPackageName() + ", "
+ + "className = " + mComponentName.getClassName() + ", "
+ "uriScheme = " + mUriScheme + ", "
+ "uriHost = " + mUriHost + ", "
+ "uriPrefix = " + mUriPrefix
@@ -105,8 +104,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mPackageName);
- dest.writeString(mClassName);
+ dest.writeString(mComponentName.getPackageName());
+ dest.writeString(mComponentName.getClassName());
dest.writeString(mUriScheme);
dest.writeString(mUriHost);
dest.writeString(mUriPrefix);
@@ -124,12 +123,11 @@
String uriHost = in.readString();
String uriPrefix = in.readString();
- this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mPackageName);
- this.mClassName = className;
+ NonNull.class, null, packageName);
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mClassName);
+ NonNull.class, null, className);
+ this.mComponentName = new ComponentName(packageName, className);
this.mUriScheme = uriScheme;
this.mUriHost = uriHost;
this.mUriPrefix = uriPrefix;
@@ -174,28 +172,10 @@
}
/**
- * Sets package name of the App link.
- */
- @NonNull
- public Builder setPackageName(@NonNull String value) {
- mPackageName = value;
- return this;
- }
-
- /**
- * Sets app name of the App link.
- */
- @NonNull
- public Builder setClassName(@NonNull String value) {
- mClassName = value;
- return this;
- }
-
- /**
* Sets URI scheme of the App link.
*/
@NonNull
- public Builder setUriScheme(@Nullable String value) {
+ public Builder setUriScheme(@NonNull String value) {
mUriScheme = value;
return this;
}
@@ -204,7 +184,7 @@
* Sets URI host of the App link.
*/
@NonNull
- public Builder setUriHost(@Nullable String value) {
+ public Builder setUriHost(@NonNull String value) {
mUriHost = value;
return this;
}
@@ -213,7 +193,7 @@
* Sets URI prefix of the App link.
*/
@NonNull
- public Builder setUriPrefix(@Nullable String value) {
+ public Builder setUriPrefix(@NonNull String value) {
mUriPrefix = value;
return this;
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 3111ee7..9eb4a6c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -761,7 +761,12 @@
}
/**
- * Registers app link info.
+ * Registers an Android application link info record which can be used to launch the specific
+ * Android application by TV interactive App RTE.
+ *
+ * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
+ * ID can be found in {@link TvInputInfo#getId()}.
+ * @param appLinkInfo The Android application link info record to be registered.
*/
public void registerAppLinkInfo(
@NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
@@ -773,7 +778,12 @@
}
/**
- * Unregisters app link info.
+ * Unregisters an Android application link info record which can be used to launch the specific
+ * Android application by TV interactive App RTE.
+ *
+ * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
+ * ID can be found in {@link TvInputInfo#getId()}.
+ * @param appLinkInfo The Android application link info record to be unregistered.
*/
public void unregisterAppLinkInfo(
@NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
@@ -1814,8 +1824,8 @@
}
/**
- * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is
- * called.
+ * This is called when {@link TvInteractiveAppService.Session#notifyTeletextAppStateChanged}
+ * is called.
*
* @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
* @param state the current state.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 9a0818e..d22fd83 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -20,6 +20,7 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -77,15 +78,14 @@
private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
- // TODO: cleanup and unhide APIs.
-
/**
* This is the interface name that a service implementing a TV Interactive App service should
* say that it supports -- that is, this is the action it uses for its intent filter. To be
* supported, the service must also require the
- * android.Manifest.permission#BIND_TV_INTERACTIVE_APP permission so that other applications
- * cannot abuse it.
+ * {@link android.Manifest.permission#BIND_TV_INTERACTIVE_APP} permission so that other
+ * applications cannot abuse it.
*/
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
"android.media.tv.interactive.TvInteractiveAppService";
@@ -245,13 +245,13 @@
public abstract void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type);
/**
- * Registers App link info.
+ * Called when a request to register an Android application link info record is received.
*/
public void onRegisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
}
/**
- * Unregisters App link info.
+ * Called when a request to unregister an Android application link info record is received.
*/
public void onUnregisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
}
@@ -385,7 +385,7 @@
}
/**
- * Resets TvIAppService session.
+ * Resets TvInteractiveAppService session.
*/
public void onResetInteractiveApp() {
}
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index 50a2083..3b70890 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -167,10 +167,10 @@
private Lnb() {}
- void setCallbackAndOwner(Executor executor, @Nullable LnbCallback callback, Tuner tuner) {
+ void setCallbackAndOwner(Tuner tuner, Executor executor, @Nullable LnbCallback callback) {
synchronized (mCallbackLock) {
if (callback != null && executor != null) {
- addCallback(callback, executor);
+ addCallback(executor, callback);
}
}
setOwner(tuner);
@@ -179,12 +179,12 @@
/**
* Adds LnbCallback
*
- * @param callback the callback to receive notifications from LNB.
* @param executor the executor on which callback will be invoked. Cannot be null.
+ * @param callback the callback to receive notifications from LNB.
*/
- public void addCallback(@NonNull LnbCallback callback, @NonNull Executor executor) {
- Objects.requireNonNull(callback, "callback must not be null");
+ public void addCallback(@NonNull Executor executor, @NonNull LnbCallback callback) {
Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
synchronized (mCallbackLock) {
mCallbackMap.put(callback, executor);
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 1e32cad..ef0270b 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -62,7 +62,9 @@
import android.os.Message;
import android.os.Process;
import android.util.Log;
+
import com.android.internal.util.FrameworkStatsLog;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -2147,12 +2149,12 @@
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "LnbCallback must not be null");
if (mLnb != null) {
- mLnb.setCallbackAndOwner(executor, cb, this);
+ mLnb.setCallbackAndOwner(this, executor, cb);
return mLnb;
}
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
&& mLnb != null) {
- mLnb.setCallbackAndOwner(executor, cb, this);
+ mLnb.setCallbackAndOwner(this, executor, cb);
setLnb(mLnb);
return mLnb;
}
@@ -2186,7 +2188,7 @@
mLnbHandle = null;
}
mLnb = newLnb;
- mLnb.setCallbackAndOwner(executor, cb, this);
+ mLnb.setCallbackAndOwner(this, executor, cb);
setLnb(mLnb);
}
return mLnb;
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 6652780..217a1f67c3 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -154,17 +154,17 @@
],
}
+// TODO: remove this empty filegroup.
filegroup {
name: "framework-connectivity-tiramisu-sources",
- srcs: [
- ":framework-connectivity-ethernet-sources",
- ],
+ srcs: [],
visibility: ["//frameworks/base"],
}
filegroup {
name: "framework-connectivity-tiramisu-updatable-sources",
srcs: [
+ ":framework-connectivity-ethernet-sources",
":framework-connectivity-ipsec-sources",
":framework-connectivity-netstats-sources",
":framework-connectivity-nsd-sources",
diff --git a/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
index 9bffbfb..61b34d0 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
@@ -34,8 +34,9 @@
private ConnectivityFrameworkInitializerTiramisu() {}
/**
- * Called by {@link SystemServiceRegistry}'s static initializer and registers nsd services to
- * {@link Context}, so that {@link Context#getSystemService} can return them.
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers NetworkStats, nsd,
+ * ipsec and ethernet services to {@link Context}, so that {@link Context#getSystemService} can
+ * return them.
*
* @throws IllegalStateException if this is called anywhere besides
* {@link SystemServiceRegistry}.
@@ -68,5 +69,14 @@
return new NetworkStatsManager(context, service);
}
);
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.ETHERNET_SERVICE,
+ EthernetManager.class,
+ (context, serviceBinder) -> {
+ IEthernetManager service = IEthernetManager.Stub.asInterface(serviceBinder);
+ return new EthernetManager(context, service);
+ }
+ );
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index 72243f9..793f28d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -33,7 +33,7 @@
import android.os.RemoteException;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
+import com.android.modules.utils.BackgroundThread;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -511,7 +511,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void updateConfiguration(
@NonNull String iface,
@NonNull EthernetNetworkUpdateRequest request,
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
index a626971..43f4c40f 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
@@ -24,36 +24,52 @@
import java.util.Objects;
-/** @hide */
+/**
+ * Represents a request to update an existing Ethernet interface.
+ *
+ * @see EthernetManager#updateConfiguration
+ *
+ * @hide
+ */
@SystemApi
public final class EthernetNetworkUpdateRequest implements Parcelable {
@NonNull
private final IpConfiguration mIpConfig;
- @NonNull
+ @Nullable
private final NetworkCapabilities mNetworkCapabilities;
+ /**
+ * @return the new {@link IpConfiguration}.
+ */
@NonNull
public IpConfiguration getIpConfiguration() {
return new IpConfiguration(mIpConfig);
}
- @NonNull
+ /**
+ * Setting the {@link NetworkCapabilities} is optional in {@link EthernetNetworkUpdateRequest}.
+ * When set to null, the existing NetworkCapabilities are not updated.
+ *
+ * @return the new {@link NetworkCapabilities} or null.
+ */
+ @Nullable
public NetworkCapabilities getNetworkCapabilities() {
- return new NetworkCapabilities(mNetworkCapabilities);
+ return mNetworkCapabilities == null ? null : new NetworkCapabilities(mNetworkCapabilities);
}
private EthernetNetworkUpdateRequest(@NonNull final IpConfiguration ipConfig,
- @NonNull final NetworkCapabilities networkCapabilities) {
+ @Nullable final NetworkCapabilities networkCapabilities) {
Objects.requireNonNull(ipConfig);
- Objects.requireNonNull(networkCapabilities);
- mIpConfig = new IpConfiguration(ipConfig);
- mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
+ mIpConfig = ipConfig;
+ mNetworkCapabilities = networkCapabilities;
}
private EthernetNetworkUpdateRequest(@NonNull final Parcel source) {
Objects.requireNonNull(source);
- mIpConfig = IpConfiguration.CREATOR.createFromParcel(source);
- mNetworkCapabilities = NetworkCapabilities.CREATOR.createFromParcel(source);
+ mIpConfig = source.readParcelable(IpConfiguration.class.getClassLoader(),
+ IpConfiguration.class);
+ mNetworkCapabilities = source.readParcelable(NetworkCapabilities.class.getClassLoader(),
+ NetworkCapabilities.class);
}
/**
@@ -75,7 +91,8 @@
public Builder(@NonNull final EthernetNetworkUpdateRequest request) {
Objects.requireNonNull(request);
mBuilderIpConfig = new IpConfiguration(request.mIpConfig);
- mBuilderNetworkCapabilities = new NetworkCapabilities(request.mNetworkCapabilities);
+ mBuilderNetworkCapabilities = null == request.mNetworkCapabilities
+ ? null : new NetworkCapabilities(request.mNetworkCapabilities);
}
/**
@@ -85,7 +102,6 @@
*/
@NonNull
public Builder setIpConfiguration(@NonNull final IpConfiguration ipConfig) {
- Objects.requireNonNull(ipConfig);
mBuilderIpConfig = new IpConfiguration(ipConfig);
return this;
}
@@ -96,9 +112,8 @@
* @return The builder to facilitate chaining.
*/
@NonNull
- public Builder setNetworkCapabilities(@NonNull final NetworkCapabilities nc) {
- Objects.requireNonNull(nc);
- mBuilderNetworkCapabilities = new NetworkCapabilities(nc);
+ public Builder setNetworkCapabilities(@Nullable final NetworkCapabilities nc) {
+ mBuilderNetworkCapabilities = nc == null ? null : new NetworkCapabilities(nc);
return this;
}
@@ -135,8 +150,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- mIpConfig.writeToParcel(dest, flags);
- mNetworkCapabilities.writeToParcel(dest, flags);
+ dest.writeParcelable(mIpConfig, flags);
+ dest.writeParcelable(mNetworkCapabilities, flags);
}
@Override
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 5100e7c..4b799c5 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -102,17 +102,16 @@
],
path: "src",
visibility: [
- "//frameworks/opt/net/ethernet",
+ "//frameworks/opt/net/ethernet/tests",
],
}
// Connectivity-T common libraries.
+// TODO: remove this empty filegroup.
filegroup {
name: "services.connectivity-tiramisu-sources",
- srcs: [
- ":services.connectivity-ethernet-sources",
- ],
+ srcs: [],
path: "src",
visibility: ["//frameworks/base/services/core"],
}
@@ -120,6 +119,7 @@
filegroup {
name: "services.connectivity-tiramisu-updatable-sources",
srcs: [
+ ":services.connectivity-ethernet-sources",
":services.connectivity-ipsec-sources",
":services.connectivity-netstats-sources",
":services.connectivity-nsd-sources",
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index b5e0539..1d22908 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -565,7 +565,7 @@
return new BpfMap<U32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
U32.class, U8.class);
} catch (ErrnoException e) {
- Log.wtf(TAG, "Cannot create uid counter set map: " + e);
+ Log.wtf(TAG, "Cannot open uid counter set map: " + e);
return null;
}
}
@@ -576,7 +576,7 @@
return new BpfMap<CookieTagMapKey, CookieTagMapValue>(COOKIE_TAG_MAP_PATH,
BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
- Log.wtf(TAG, "Cannot create cookie tag map: " + e);
+ Log.wtf(TAG, "Cannot open cookie tag map: " + e);
return null;
}
}
@@ -587,7 +587,7 @@
return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_A_PATH,
BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
} catch (ErrnoException e) {
- Log.wtf(TAG, "Cannot create stats map A: " + e);
+ Log.wtf(TAG, "Cannot open stats map A: " + e);
return null;
}
}
@@ -598,7 +598,7 @@
return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_B_PATH,
BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
} catch (ErrnoException e) {
- Log.wtf(TAG, "Cannot create stats map B: " + e);
+ Log.wtf(TAG, "Cannot open stats map B: " + e);
return null;
}
}
@@ -609,7 +609,7 @@
return new BpfMap<UidStatsMapKey, StatsMapValue>(APP_UID_STATS_MAP_PATH,
BpfMap.BPF_F_RDWR, UidStatsMapKey.class, StatsMapValue.class);
} catch (ErrnoException e) {
- Log.wtf(TAG, "Cannot create app uid stats map: " + e);
+ Log.wtf(TAG, "Cannot open app uid stats map: " + e);
return null;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java b/packages/ConnectivityT/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
similarity index 92%
rename from services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java
rename to packages/ConnectivityT/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
index 2f77126..ad0be58 100644
--- a/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java
+++ b/packages/ConnectivityT/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
@@ -48,10 +48,16 @@
*/
@RunWith(AndroidJUnit4.class)
public class IpConfigStoreTest {
+ private static final int KEY_CONFIG = 17;
+ private static final String IFACE_1 = "eth0";
+ private static final String IFACE_2 = "eth1";
+ private static final String IP_ADDR_1 = "192.168.1.10/24";
+ private static final String IP_ADDR_2 = "192.168.1.20/24";
+ private static final String DNS_IP_ADDR_1 = "1.2.3.4";
+ private static final String DNS_IP_ADDR_2 = "5.6.7.8";
@Test
public void backwardCompatibility2to3() throws IOException {
- final int KEY_CONFIG = 17;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(byteStream);
@@ -73,13 +79,6 @@
@Test
public void staticIpMultiNetworks() throws Exception {
- final String IFACE_1 = "eth0";
- final String IFACE_2 = "eth1";
- final String IP_ADDR_1 = "192.168.1.10/24";
- final String IP_ADDR_2 = "192.168.1.20/24";
- final String DNS_IP_ADDR_1 = "1.2.3.4";
- final String DNS_IP_ADDR_2 = "5.6.7.8";
-
final ArrayList<InetAddress> dnsServers = new ArrayList<>();
dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_1));
dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_2));
@@ -144,11 +143,11 @@
/** Synchronously writes into given byte steam */
private static class MockedDelayedDiskWrite extends DelayedDiskWrite {
- final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream();
@Override
public void write(String filePath, Writer w) {
- DataOutputStream outputStream = new DataOutputStream(byteStream);
+ DataOutputStream outputStream = new DataOutputStream(mByteStream);
try {
w.onWriteCalled(outputStream);
diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
index 44b3b4e..706aba3d 100644
--- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
+++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
@@ -51,27 +51,34 @@
}
/**
- * Whether current activity is embedded in the Settings app or not.
+ * Whether the current activity is embedded in the Settings app or not.
+ *
+ * @param activity Activity that needs the check
*/
public static boolean isActivityEmbedded(Activity activity) {
return SplitController.getInstance().isActivityEmbedded(activity);
}
/**
- * Whether current activity is suggested to show back button or not.
+ * Whether the current activity should hide the navigate up button.
+ *
+ * @param activity Activity that needs the check
+ * @param isSecondLayerPage indicates if the activity(page) is shown in the 2nd layer of
+ * Settings app
*/
- public static boolean shouldHideBackButton(Activity activity, boolean isSecondaryLayerPage) {
+ public static boolean shouldHideNavigateUpButton(Activity activity, boolean isSecondLayerPage) {
if (!BuildCompat.isAtLeastT()) {
return false;
}
- if (!isSecondaryLayerPage) {
+ if (!isSecondLayerPage) {
return false;
}
- final String shouldHideBackButton = Settings.Global.getString(activity.getContentResolver(),
- "settings_hide_secondary_page_back_button_in_two_pane");
+ final String shouldHideNavigateUpButton =
+ Settings.Global.getString(activity.getContentResolver(),
+ "settings_hide_second_layer_page_navigate_up_button_in_two_pane");
- if (TextUtils.isEmpty(shouldHideBackButton)
- || TextUtils.equals("true", shouldHideBackButton)) {
+ if (TextUtils.isEmpty(shouldHideNavigateUpButton)
+ || Boolean.parseBoolean(shouldHideNavigateUpButton)) {
return isActivityEmbedded(activity);
}
return false;
diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/res/values-v31/styles.xml
index 343de2c..0b703c9 100644
--- a/packages/SettingsLib/res/values-v31/styles.xml
+++ b/packages/SettingsLib/res/values-v31/styles.xml
@@ -15,7 +15,8 @@
limitations under the License.
-->
<resources>
- <style name="SettingsLibTabsTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <style name="SettingsLibTabsTextAppearance"
+ parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
<item name="android:textSize">16sp</item>
</style>
@@ -25,6 +26,7 @@
<item name="android:layout_height">48dp</item>
<item name="android:layout_marginStart">?android:attr/listPreferredItemPaddingStart</item>
<item name="android:layout_marginEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ <item name="tabMaxWidth">0dp</item>
<item name="tabGravity">fill</item>
<item name="tabBackground">@drawable/settingslib_tabs_background</item>
<item name="tabIndicator">@drawable/settingslib_tabs_indicator_background</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e524405..042fef2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1572,4 +1572,10 @@
<!-- Content description for a default user icon. [CHAR LIMIT=NONE] -->
<string name="default_user_icon_description">Default user icon</string>
+ <!-- Title for the 'physical keyboard' settings screen. [CHAR LIMIT=35] -->
+ <string name="physical_keyboard_title">Physical keyboard</string>
+ <!-- Title for the keyboard layout preference dialog. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_dialog_title">Choose keyboard layout</string>
+ <!-- Label of the default keyboard layout. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_default_label">Default</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 17db45d..e4efae2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -23,7 +23,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
-import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -33,8 +32,6 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
-import com.android.internal.util.Preconditions;
-
/**
* Helper class for managing settings preferences that can be disabled
* by device admins via user restrictions.
@@ -42,8 +39,8 @@
public class RestrictedPreferenceHelper {
private final Context mContext;
private final Preference mPreference;
- final String packageName;
- final int uid;
+ String packageName;
+ int uid;
private boolean mDisabledByAdmin;
private EnforcedAdmin mEnforcedAdmin;
@@ -219,6 +216,11 @@
return mDisabledByAppOps;
}
+ public void updatePackageDetails(String packageName, int uid) {
+ this.packageName = packageName;
+ this.uid = uid;
+ }
+
private void updateDisabledState() {
if (!(mPreference instanceof RestrictedTopLevelPreference)) {
mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index c607d74..091e322 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -18,8 +18,11 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Process;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -28,6 +31,7 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
@@ -39,6 +43,7 @@
*/
public class RestrictedSwitchPreference extends SwitchPreference {
RestrictedPreferenceHelper mHelper;
+ AppOpsManager mAppOpsManager;
boolean mUseAdditionalSummary = false;
CharSequence mRestrictedSwitchSummary;
private int mIconSize;
@@ -90,6 +95,11 @@
this(context, null);
}
+ @VisibleForTesting
+ public void setAppOps(AppOpsManager appOps) {
+ mAppOpsManager = appOps;
+ }
+
public void setIconSize(int iconSize) {
mIconSize = iconSize;
}
@@ -97,6 +107,12 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ final View switchView = holder.findViewById(android.R.id.switch_widget);
+ if (switchView != null) {
+ final View rootView = switchView.getRootView();
+ rootView.setFilterTouchesWhenObscured(true);
+ }
+
mHelper.onBindViewHolder(holder);
CharSequence switchSummary;
@@ -164,11 +180,18 @@
@Override
public void setEnabled(boolean enabled) {
+ boolean changed = false;
if (enabled && isDisabledByAdmin()) {
mHelper.setDisabledByAdmin(null);
- return;
+ changed = true;
}
- super.setEnabled(enabled);
+ if (enabled && isDisabledByAppOps()) {
+ mHelper.setDisabledByAppOps(false);
+ changed = true;
+ }
+ if (!changed) {
+ super.setEnabled(enabled);
+ }
}
public void setDisabledByAdmin(EnforcedAdmin admin) {
@@ -180,4 +203,38 @@
public boolean isDisabledByAdmin() {
return mHelper.isDisabledByAdmin();
}
+
+ private void setDisabledByAppOps(boolean disabled) {
+ if (mHelper.setDisabledByAppOps(disabled)) {
+ notifyChanged();
+ }
+ }
+
+ public boolean isDisabledByAppOps() {
+ return mHelper.isDisabledByAppOps();
+ }
+
+ public int getUid() {
+ return mHelper != null ? mHelper.uid : Process.INVALID_UID;
+ }
+
+ public String getPackageName() {
+ return mHelper != null ? mHelper.packageName : null;
+ }
+
+ public void updateState(@NonNull String packageName, int uid, boolean isEnabled) {
+ mHelper.updatePackageDetails(packageName, uid);
+ if (mAppOpsManager == null) {
+ mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
+ }
+ final int mode = mAppOpsManager.noteOpNoThrow(
+ AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid, packageName);
+ final boolean appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED;
+ if (appOpsAllowed || isEnabled) {
+ setEnabled(true);
+ } else {
+ setDisabledByAppOps(true);
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 1c9d9cf..3d91c5a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -285,6 +285,12 @@
public void disconnect() {
synchronized (mProfileLock) {
+ if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ for (CachedBluetoothDevice member : getMemberDevice()) {
+ Log.d(TAG, "Disconnect the member(" + member.getAddress() + ")");
+ member.disconnect();
+ }
+ }
mDevice.disconnect();
}
// Disconnect PBAP server in case its connected
@@ -399,6 +405,12 @@
}
mDevice.connect();
+ if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ for (CachedBluetoothDevice member : getMemberDevice()) {
+ Log.d(TAG, "connect the member(" + member.getAddress() + ")");
+ member.connect();
+ }
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 4ab6542..9ef6bdf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -43,6 +43,31 @@
private static final int INVALID_RSSI = -127;
/**
+ * The intent action shows Wi-Fi dialog to connect Wi-Fi network.
+ * <p>
+ * Input: The calling package should put the chosen
+ * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+ * the {@link #EXTRA_CHOSEN_WIFI_ENTRY_KEY}.
+ * <p>
+ * Output: Nothing.
+ */
+ @VisibleForTesting
+ static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
+
+ /**
+ * Specify a key that indicates the WifiEntry to be configured.
+ */
+ @VisibleForTesting
+ static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+
+ /**
+ * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to.
+ * {@code true} means a chosen WifiEntry request to connect to.
+ */
+ @VisibleForTesting
+ static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
+
+ /**
* The intent action shows network details settings to allow configuration of Wi-Fi.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -325,6 +350,19 @@
}
/**
+ * Returns the Intent for Wi-Fi dialog.
+ *
+ * @param key The Wi-Fi entry key
+ * @param connectForCaller True if a chosen WifiEntry request to connect to
+ */
+ public static Intent getWifiDialogIntent(String key, boolean connectForCaller) {
+ final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+ intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key);
+ intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller);
+ return intent;
+ }
+
+ /**
* Returns the Intent for Wi-Fi network details settings.
*
* @param key The Wi-Fi entry key
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
index 1d08711..d988111 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
@@ -89,6 +89,10 @@
@After
public void tearDown() {
+ String[] entries = mImagesDir.list();
+ for (String entry : entries) {
+ new File(mImagesDir, entry).delete();
+ }
mImagesDir.delete();
}
@@ -233,7 +237,8 @@
public void internalCropUsedIfNoSystemCropperFound() throws IOException {
when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
- new File(mImagesDir, "file.txt").createNewFile();
+ File file = new File(mImagesDir, "file.txt");
+ saveBitmapToFile(file);
Intent intent = new Intent();
intent.setData(Uri.parse(
@@ -241,10 +246,6 @@
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
- Intent startIntent = verifyStartSystemActivityForResult(
- "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
- assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
-
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index e7b3fe9..6956105 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -156,6 +156,27 @@
}
@Test
+ public void getWifiDialogIntent_returnsCorrectValues() {
+ String key = "test_key";
+
+ // Test that connectForCaller is true.
+ Intent intent = WifiUtils.getWifiDialogIntent(key, true /* connectForCaller */);
+
+ assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DIALOG);
+ assertThat(intent.getStringExtra(WifiUtils.EXTRA_CHOSEN_WIFI_ENTRY_KEY)).isEqualTo(key);
+ assertThat(intent.getBooleanExtra(WifiUtils.EXTRA_CONNECT_FOR_CALLER, true))
+ .isEqualTo(true /* connectForCaller */);
+
+ // Test that connectForCaller is false.
+ intent = WifiUtils.getWifiDialogIntent(key, false /* connectForCaller */);
+
+ assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DIALOG);
+ assertThat(intent.getStringExtra(WifiUtils.EXTRA_CHOSEN_WIFI_ENTRY_KEY)).isEqualTo(key);
+ assertThat(intent.getBooleanExtra(WifiUtils.EXTRA_CONNECT_FOR_CALLER, true))
+ .isEqualTo(false /* connectForCaller */);
+ }
+
+ @Test
public void getWifiDetailsSettingsIntent_returnsCorrectValues() {
final String key = "test_key";
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index fa3360c..cbd71c0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -328,7 +328,5 @@
return true;
});
VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.FAST_PAIR_SCAN_ENABLED, BOOLEAN_VALIDATOR);
-
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7f8b2f5..e24b2d6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -794,7 +794,7 @@
android:noHistory="true"
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
@@ -805,7 +805,7 @@
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index ef60a24..4e9a4be 100644
--- a/packages/SystemUI/animation/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -15,5 +15,14 @@
limitations under the License.
-->
<resources>
+ <!-- DialogLaunchAnimator -->
<item type="id" name="launch_animation_running"/>
+
+ <!-- ViewBoundsAnimator -->
+ <item type="id" name="tag_animator"/>
+ <item type="id" name="tag_layout_listener"/>
+ <item type="id" name="tag_override_bottom"/>
+ <item type="id" name="tag_override_left"/>
+ <item type="id" name="tag_override_right"/>
+ <item type="id" name="tag_override_top"/>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt
new file mode 100644
index 0000000..5593fdf
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt
@@ -0,0 +1,328 @@
+/*
+ * 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.systemui.animation
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.animation.PropertyValuesHolder
+import android.util.IntProperty
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Interpolator
+
+/**
+ * A class that allows changes in bounds within a view hierarchy to animate seamlessly between the
+ * start and end state.
+ */
+class ViewBoundAnimator {
+ // TODO(b/221418522): make this private once it can't be passed as an arg anymore.
+ enum class Bound(val label: String, val overrideTag: Int) {
+ LEFT("left", R.id.tag_override_left) {
+ override fun setValue(view: View, value: Int) {
+ view.left = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.left
+ }
+ },
+ TOP("top", R.id.tag_override_top) {
+ override fun setValue(view: View, value: Int) {
+ view.top = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.top
+ }
+ },
+ RIGHT("right", R.id.tag_override_right) {
+ override fun setValue(view: View, value: Int) {
+ view.right = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.right
+ }
+ },
+ BOTTOM("bottom", R.id.tag_override_bottom) {
+ override fun setValue(view: View, value: Int) {
+ view.bottom = value
+ }
+
+ override fun getValue(view: View): Int {
+ return view.bottom
+ }
+ };
+
+ abstract fun setValue(view: View, value: Int)
+ abstract fun getValue(view: View): Int
+ }
+
+ companion object {
+ /** Default values for the animation. These can all be overridden at call time. */
+ private const val DEFAULT_DURATION = 500L
+ private val DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED
+ private val DEFAULT_BOUNDS = setOf(Bound.LEFT, Bound.TOP, Bound.RIGHT, Bound.BOTTOM)
+
+ /** The properties used to animate the view bounds. */
+ private val PROPERTIES = mapOf(
+ Bound.LEFT to createViewProperty(Bound.LEFT),
+ Bound.TOP to createViewProperty(Bound.TOP),
+ Bound.RIGHT to createViewProperty(Bound.RIGHT),
+ Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
+ )
+
+ private fun createViewProperty(bound: Bound): IntProperty<View> {
+ return object : IntProperty<View>(bound.label) {
+ override fun setValue(view: View, value: Int) {
+ setBound(view, bound, value)
+ }
+
+ override fun get(view: View): Int {
+ return getBound(view, bound) ?: bound.getValue(view)
+ }
+ }
+ }
+
+ /**
+ * Instruct the animator to watch for changes to the layout of [rootView] and its children
+ * and animate them. The animation can be limited to a subset of [bounds]. It uses the
+ * given [interpolator] and [duration].
+ *
+ * If a new layout change happens while an animation is already in progress, the animation
+ * is updated to continue from the current values to the new end state.
+ *
+ * The animator continues to respond to layout changes until [stopAnimating] is called.
+ *
+ * Successive calls to this method override the previous settings ([interpolator] and
+ * [duration]). The changes take effect on the next animation.
+ *
+ * TODO(b/221418522): remove the ability to select which bounds to animate and always
+ * animate all of them.
+ */
+ @JvmOverloads
+ fun animate(
+ rootView: View,
+ bounds: Set<Bound> = DEFAULT_BOUNDS,
+ interpolator: Interpolator = DEFAULT_INTERPOLATOR,
+ duration: Long = DEFAULT_DURATION
+ ) {
+ animate(rootView, bounds, interpolator, duration, false /* ephemeral */)
+ }
+
+ /**
+ * Like [animate], but only takes effect on the next layout update, then unregisters itself
+ * once the first animation is complete.
+ *
+ * TODO(b/221418522): remove the ability to select which bounds to animate and always
+ * animate all of them.
+ */
+ @JvmOverloads
+ fun animateNextUpdate(
+ rootView: View,
+ bounds: Set<Bound> = DEFAULT_BOUNDS,
+ interpolator: Interpolator = DEFAULT_INTERPOLATOR,
+ duration: Long = DEFAULT_DURATION
+ ) {
+ animate(rootView, bounds, interpolator, duration, true /* ephemeral */)
+ }
+
+ private fun animate(
+ rootView: View,
+ bounds: Set<Bound>,
+ interpolator: Interpolator,
+ duration: Long,
+ ephemeral: Boolean
+ ) {
+ val listener = createListener(bounds, interpolator, duration, ephemeral)
+ recursivelyAddListener(rootView, listener)
+ }
+
+ /**
+ * Instruct the animator to stop watching for changes to the layout of [rootView] and its
+ * children.
+ *
+ * Any animations already in progress continue until their natural conclusion.
+ */
+ fun stopAnimating(rootView: View) {
+ val listener = rootView.getTag(R.id.tag_layout_listener)
+ if (listener != null && listener is View.OnLayoutChangeListener) {
+ rootView.setTag(R.id.tag_layout_listener, null /* tag */)
+ rootView.removeOnLayoutChangeListener(listener)
+ }
+
+ if (rootView is ViewGroup) {
+ for (i in 0 until rootView.childCount) {
+ stopAnimating(rootView.getChildAt(i))
+ }
+ }
+ }
+
+ private fun createListener(
+ bounds: Set<Bound>,
+ interpolator: Interpolator,
+ duration: Long,
+ ephemeral: Boolean
+ ): View.OnLayoutChangeListener {
+ return object : View.OnLayoutChangeListener {
+ override fun onLayoutChange(
+ view: View?,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ if (view == null) return
+
+ val startLeft = getBound(view, Bound.LEFT) ?: oldLeft
+ val startTop = getBound(view, Bound.TOP) ?: oldTop
+ val startRight = getBound(view, Bound.RIGHT) ?: oldRight
+ val startBottom = getBound(view, Bound.BOTTOM) ?: oldBottom
+
+ (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
+
+ if (view.visibility == View.GONE || view.visibility == View.INVISIBLE) {
+ setBound(view, Bound.LEFT, left)
+ setBound(view, Bound.TOP, top)
+ setBound(view, Bound.RIGHT, right)
+ setBound(view, Bound.BOTTOM, bottom)
+ return
+ }
+
+ val startValues = mapOf(
+ Bound.LEFT to startLeft,
+ Bound.TOP to startTop,
+ Bound.RIGHT to startRight,
+ Bound.BOTTOM to startBottom
+ )
+ val endValues = mapOf(
+ Bound.LEFT to left,
+ Bound.TOP to top,
+ Bound.RIGHT to right,
+ Bound.BOTTOM to bottom
+ )
+
+ val boundsToAnimate = bounds.toMutableSet()
+ if (left == startLeft) boundsToAnimate.remove(Bound.LEFT)
+ if (top == startTop) boundsToAnimate.remove(Bound.TOP)
+ if (right == startRight) boundsToAnimate.remove(Bound.RIGHT)
+ if (bottom == startBottom) boundsToAnimate.remove(Bound.BOTTOM)
+
+ if (boundsToAnimate.isNotEmpty()) {
+ startAnimation(
+ view,
+ boundsToAnimate,
+ startValues,
+ endValues,
+ interpolator,
+ duration,
+ ephemeral
+ )
+ }
+ }
+ }
+ }
+
+ private fun recursivelyAddListener(view: View, listener: View.OnLayoutChangeListener) {
+ // Make sure that only one listener is active at a time.
+ val oldListener = view.getTag(R.id.tag_layout_listener)
+ if (oldListener != null && oldListener is View.OnLayoutChangeListener) {
+ view.removeOnLayoutChangeListener(oldListener)
+ }
+
+ view.addOnLayoutChangeListener(listener)
+ view.setTag(R.id.tag_layout_listener, listener)
+ if (view is ViewGroup) {
+ for (i in 0 until view.childCount) {
+ recursivelyAddListener(view.getChildAt(i), listener)
+ }
+ }
+ }
+
+ private fun getBound(view: View, bound: Bound): Int? {
+ return view.getTag(bound.overrideTag) as? Int
+ }
+
+ private fun setBound(view: View, bound: Bound, value: Int) {
+ view.setTag(bound.overrideTag, value)
+ bound.setValue(view, value)
+ }
+
+ /**
+ * Initiates the animation of a single bound by creating the animator, registering it with
+ * the [view], and starting it. If [ephemeral], the layout change listener is unregistered
+ * at the end of the animation, so no more animations happen.
+ */
+ private fun startAnimation(
+ view: View,
+ bounds: Set<Bound>,
+ startValues: Map<Bound, Int>,
+ endValues: Map<Bound, Int>,
+ interpolator: Interpolator,
+ duration: Long,
+ ephemeral: Boolean
+ ) {
+ val propertyValuesHolders = buildList {
+ bounds.forEach { bound ->
+ add(
+ PropertyValuesHolder.ofInt(
+ PROPERTIES[bound],
+ startValues.getValue(bound),
+ endValues.getValue(bound)
+ )
+ )
+ }
+ }.toTypedArray()
+
+ val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders)
+ animator.interpolator = interpolator
+ animator.duration = duration
+ animator.addListener(object : AnimatorListenerAdapter() {
+ var cancelled = false
+
+ override fun onAnimationEnd(animation: Animator) {
+ view.setTag(R.id.tag_animator, null /* tag */)
+ bounds.forEach { view.setTag(it.overrideTag, null /* tag */) }
+
+ // When an animation is cancelled, a new one might be taking over. We shouldn't
+ // unregister the listener yet.
+ if (ephemeral && !cancelled) {
+ val listener = view.getTag(R.id.tag_layout_listener)
+ if (listener != null && listener is View.OnLayoutChangeListener) {
+ view.setTag(R.id.tag_layout_listener, null /* tag */)
+ view.removeOnLayoutChangeListener(listener)
+ }
+ }
+ }
+
+ override fun onAnimationCancel(animation: Animator?) {
+ cancelled = true
+ }
+ })
+
+ bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) }
+
+ view.setTag(R.id.tag_animator, animator)
+ animator.start()
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml b/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml
new file mode 100644
index 0000000..9e61236
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.systemui.media.SquigglyProgress />
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 6375698..625ffd3 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -17,13 +17,14 @@
*/
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- Keyguard PIN pad styles -->
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
<item name="android:textSize">14dp</item>
<item name="android:background">@drawable/kg_emergency_button_background</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
index 8f6753a..da4088f 100644
--- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -36,6 +36,7 @@
android:tooltipText="@*android:string/share"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/copy_button"
+ android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_screenshot_share" />
<ScrollView
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9ad0152..f030f31 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -44,6 +44,7 @@
android:background="@drawable/qs_media_scrim"
/>
+ <!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
android:layout_width="wrap_content"
@@ -51,6 +52,14 @@
android:orientation="vertical"
app:layout_constraintGuide_percent="0.6" />
+ <!-- Guideline for action buttons (collapsed view only) -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/action_button_guideline"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_guideline" />
+
<!-- App icon -->
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
@@ -118,15 +127,7 @@
android:singleLine="true"
android:textSize="16sp"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:layout_marginStart="@dimen/qs_media_padding"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constrainedWidth="true"
- app:layout_constraintTop_toBottomOf="@id/icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
- app:layout_constraintHorizontal_bias="0" />
+ android:layout_height="wrap_content" />
<!-- Artist name -->
<TextView
@@ -136,15 +137,7 @@
style="@style/MediaPlayer.Subtitle"
android:textSize="14sp"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constrainedWidth="true"
- android:layout_marginTop="0dp"
- app:layout_constraintTop_toBottomOf="@id/header_title"
- app:layout_constraintStart_toStartOf="@id/header_title"
- app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
- app:layout_constraintBottom_toBottomOf="@id/actionPlayPause"
- app:layout_constraintHorizontal_bias="0" />
+ android:layout_height="wrap_content" />
<ImageButton
android:id="@+id/actionPlayPause"
@@ -153,9 +146,33 @@
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
- android:layout_marginTop="0dp"
- android:layout_marginBottom="0dp" />
+ />
+ <!-- See comment in media_session_collapsed.xml for how these barriers are used -->
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/media_action_barrier"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="vertical"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:barrierDirection="start"
+ app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4"
+ />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/media_action_barrier_end"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="vertical"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <!-- Button visibility will be controlled in code -->
<ImageButton
android:id="@+id/actionPrev"
style="@style/MediaPlayer.SessionAction"
@@ -163,10 +180,9 @@
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="0dp"
- android:layout_marginBottom="0dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintHorizontal_chainStyle="packed" />
+ />
<!-- Seek Bar -->
<!-- As per Material Design on Bidirectionality, this is forced to LTR in code -->
@@ -174,12 +190,12 @@
android:id="@+id/media_progress_bar"
style="@style/MediaPlayer.ProgressBar"
android:layout_width="0dp"
- android:layout_height="wrap_content"
+ android:layout_height="48dp"
android:paddingTop="@dimen/qs_media_session_enabled_seekbar_vertical_padding"
android:paddingBottom="12dp"
android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
android:splitTrack="false"
- android:layout_marginBottom="0dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp" />
@@ -191,29 +207,58 @@
android:layout_height="48dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
- android:layout_marginBottom="0dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<ImageButton
- android:id="@+id/actionStart"
+ android:id="@+id/action0"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
- android:layout_marginBottom="0dp"
- android:layout_marginTop="0dp" />
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp"/>
<ImageButton
- android:id="@+id/actionEnd"
+ android:id="@+id/action1"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="4dp"
- android:layout_marginBottom="0dp"
- android:layout_marginTop="0dp"
- app:layout_constraintHorizontal_chainStyle="packed" />
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp" />
+
+ <ImageButton
+ android:id="@+id/action2"
+ style="@style/MediaPlayer.SessionAction"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="@dimen/qs_media_action_spacing"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp" />
+
+ <ImageButton
+ android:id="@+id/action3"
+ style="@style/MediaPlayer.SessionAction"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="@dimen/qs_media_action_spacing"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp" />
+
+ <ImageButton
+ android:id="@+id/action4"
+ style="@style/MediaPlayer.SessionAction"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="@dimen/qs_media_action_spacing"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp" />
<!-- Long press menu -->
<TextView
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index c990605..f5c0509 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<resources>
- <dimen name="notification_panel_margin_horizontal">60dp</dimen>
+ <dimen name="notification_panel_margin_horizontal">48dp</dimen>
<dimen name="status_view_margin_horizontal">62dp</dimen>
<dimen name="keyguard_clock_top_margin">40dp</dimen>
<dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index e5f502f..44f8f3a 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,8 +21,11 @@
for different hardware and product builds. -->
<resources>
<dimen name="status_view_margin_horizontal">124dp</dimen>
- <dimen name="notification_panel_margin_horizontal">120dp</dimen>
<dimen name="keyguard_clock_top_margin">80dp</dimen>
<dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">90dp</dimen>
+
+ <dimen name="notification_panel_margin_horizontal">96dp</dimen>
+ <dimen name="notification_side_paddings">40dp</dimen>
+ <dimen name="notification_section_divider_height">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5ca7285..52ec516 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -139,7 +139,7 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height_increased">188dp</dimen>
- <!-- Side padding on the lockscreen on the side of notifications -->
+ <!-- Side padding on the side of notifications -->
<dimen name="notification_side_paddings">16dp</dimen>
<!-- padding between the heads up and the statusbar -->
@@ -979,6 +979,11 @@
<dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen>
<dimen name="qs_media_session_height_expanded">184dp</dimen>
<dimen name="qs_media_session_height_collapsed">128dp</dimen>
+ <dimen name="qs_media_seekbar_progress_wavelength">20dp</dimen>
+ <dimen name="qs_media_seekbar_progress_amplitude">1.5dp</dimen>
+ <dimen name="qs_media_seekbar_progress_phase">8dp</dimen>
+ <dimen name="qs_media_seekbar_progress_stroke_width">2dp</dimen>
+ <dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen>
@@ -1362,9 +1367,6 @@
<dimen name="dream_overlay_status_icon_margin">8dp</dimen>
<dimen name="dream_overlay_status_bar_icon_size">
@*android:dimen/status_bar_system_icon_size</dimen>
- <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
- shade. -->
- <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
<dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
<dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f2eaa75..3ae21e0 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -590,6 +590,7 @@
<style name="MediaPlayer.ProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal">
<item name="android:thumbTint">?android:attr/textColorPrimary</item>
+ <item name="android:progressDrawable">@drawable/media_squiggly_progress</item>
<item name="android:progressTint">?android:attr/textColorPrimary</item>
<item name="android:progressBackgroundTint">?android:attr/textColorTertiary</item>
<item name="android:clickable">true</item>
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index c6e18a6..f00e031 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -19,6 +19,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
+ android:id="@+id/media_action_barrier"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless"
+ app:layout_constraintStart_toEndOf="@id/action_button_guideline" />
+
+ <Constraint
android:id="@+id/album_art"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_media_session_height_collapsed"
@@ -28,33 +35,73 @@
app:layout_constraintBottom_toBottomOf="parent" />
<Constraint
+ android:id="@+id/header_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintEnd_toStartOf="@id/action_button_guideline"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintHorizontal_bias="0" />
+ <Constraint
+ android:id="@+id/header_artist"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp"
+ app:layout_constraintEnd_toStartOf="@id/action_button_guideline"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintStart_toStartOf="@id/header_title"
+ app:layout_constraintVertical_bias="0"
+ app:layout_constraintHorizontal_bias="0" />
+
+ <Constraint
android:id="@+id/actionPlayPause"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintStart_toEndOf="@id/actionEnd"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ app:layout_constraintVertical_bias="1"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
- app:layout_constraintBottom_toBottomOf="parent" />
+ app:layout_constraintStart_toEndOf="@id/media_action_barrier_end" />
+ <!--
+ There will only be 3 action buttons shown at most in this layout (controlled in code).
+ Play/Pause should always be at the end, but the other buttons should remain in the same order
+ when in RTL.
+ This is accomplished by setting two barriers at the start and end of the small buttons,
+ anchored to a guideline set at 3x button width from the end. The text and play/pause button are
+ positioned relative to the barriers, and the small buttons use right/left constraints to stay
+ in the correct order inside the barriers.
+ -->
<Constraint
android:id="@+id/actionPrev"
android:layout_width="48dp"
android:layout_height="48dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_bias="1"
app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintStart_toEndOf="@id/header_artist"
- app:layout_constraintEnd_toStartOf="@id/media_progress_bar"
+ app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/media_seamless" />
+ app:layout_constraintTop_toBottomOf="@id/media_seamless"
+ app:layout_constraintLeft_toRightOf="@id/media_action_barrier" />
<Constraint
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/actionPrev"
- app:layout_constraintEnd_toStartOf="@id/actionNext"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/actionPrev"
+ app:layout_constraintRight_toLeftOf="@id/actionNext"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
@@ -62,29 +109,70 @@
android:id="@+id/actionNext"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintStart_toEndOf="@id/media_progress_bar"
- app:layout_constraintEnd_toStartOf="@id/actionStart"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
+ app:layout_constraintRight_toLeftOf="@id/action0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
- android:id="@+id/actionStart"
+ android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/actionNext"
- app:layout_constraintEnd_toStartOf="@id/actionEnd"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/actionNext"
+ app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
- android:id="@+id/actionEnd"
+ android:id="@+id/action1"
android:layout_width="48dp"
android:layout_height="48dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/actionStart"
- app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/action0"
+ app:layout_constraintRight_toLeftOf="@id/action2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
-</ConstraintSet>
\ No newline at end of file
+ <Constraint
+ android:id="@+id/action2"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:visibility="gone"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/action1"
+ app:layout_constraintRight_toLeftOf="@id/action3"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless" />
+
+ <Constraint
+ android:id="@+id/action3"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:visibility="gone"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/action2"
+ app:layout_constraintRight_toLeftOf="@id/action4"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless" />
+
+ <Constraint
+ android:id="@+id/action4"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ android:visibility="gone"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/action3"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless"
+ app:layout_constraintRight_toLeftOf="@id/media_action_barrier_end" />
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index 18ec7aa..10da704 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -28,57 +28,118 @@
app:layout_constraintBottom_toBottomOf="parent" />
<Constraint
+ android:id="@+id/header_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintHorizontal_bias="0" />
+ <Constraint
+ android:id="@+id/header_artist"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ android:layout_marginTop="0dp"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
+ app:layout_constraintBottom_toTopOf="@id/media_action_barrier"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintStart_toStartOf="@id/header_title"
+ app:layout_constraintVertical_bias="0"
+ app:layout_constraintHorizontal_bias="0" />
+
+ <Constraint
android:id="@+id/actionPlayPause"
android:layout_width="48dp"
android:layout_height="48dp"
+ android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
+ android:layout_marginBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
- app:layout_constraintBottom_toTopOf="@id/actionEnd" />
+ app:layout_constraintBottom_toBottomOf="@id/header_artist" />
+ <!--
+ The bottom row of action buttons should remain in the same order when RTL, so their constraints
+ are set with right/left instead of start/end.
+ The chain is set to "spread" so that the progress bar can be weighted to fill any empty space.
+ -->
<Constraint
android:id="@+id/actionPrev"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_progress_bar"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
+ app:layout_constraintTop_toBottomOf="@id/header_artist"
+ app:layout_constraintHorizontal_chainStyle="spread" />
<Constraint
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toEndOf="@id/actionPrev"
- app:layout_constraintEnd_toStartOf="@id/actionNext"
+ android:layout_height="48dp"
+ app:layout_constraintLeft_toRightOf="@id/actionPrev"
+ app:layout_constraintRight_toLeftOf="@id/actionNext"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
+ app:layout_constraintTop_toBottomOf="@id/header_artist"
+ app:layout_constraintHorizontal_weight="1" />
<Constraint
android:id="@+id/actionNext"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintStart_toEndOf="@id/media_progress_bar"
- app:layout_constraintEnd_toStartOf="@id/actionStart"
+ app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
+ app:layout_constraintRight_toLeftOf="@id/action0"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
+ app:layout_constraintTop_toBottomOf="@id/header_artist" />
<Constraint
- android:id="@+id/actionStart"
+ android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintStart_toEndOf="@id/actionNext"
- app:layout_constraintEnd_toStartOf="@id/actionEnd"
+ app:layout_constraintLeft_toRightOf="@id/actionNext"
+ app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
+ app:layout_constraintTop_toBottomOf="@id/header_artist" />
<Constraint
- android:id="@+id/actionEnd"
+ android:id="@+id/action1"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintStart_toEndOf="@id/actionStart"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintLeft_toRightOf="@id/action0"
+ app:layout_constraintRight_toLeftOf="@id/action2"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
+ app:layout_constraintTop_toBottomOf="@id/header_artist" />
-</ConstraintSet>
\ No newline at end of file
+ <Constraint
+ android:id="@+id/action2"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ app:layout_constraintLeft_toRightOf="@id/action1"
+ app:layout_constraintRight_toLeftOf="@id/action3"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/header_artist" />
+
+ <Constraint
+ android:id="@+id/action3"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ app:layout_constraintLeft_toRightOf="@id/action2"
+ app:layout_constraintRight_toLeftOf="@id/action4"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/header_artist" />
+
+ <Constraint
+ android:id="@+id/action4"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ app:layout_constraintLeft_toRightOf="@id/action3"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/header_artist" />
+</ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 7b67917..8491f83 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -28,7 +28,7 @@
* If the transition has already started by the moment when the clients are ready to play the
* transition then it will report transition started callback and current animation progress.
*/
-class ScopedUnfoldTransitionProgressProvider
+open class ScopedUnfoldTransitionProgressProvider
@JvmOverloads
constructor(source: UnfoldTransitionProgressProvider? = null) :
UnfoldTransitionProgressProvider, TransitionProgressListener {
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index ffd1546..458d22e 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -96,7 +96,7 @@
**/
public void reloadColors() {
int color = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.textColorPrimaryInverse);
+ com.android.internal.R.attr.textColorOnAccent);
setTextColor(color);
setBackground(getContext()
.getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 1efda7e..77044ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -188,14 +188,13 @@
enableClipping(false);
setTranslationY(0);
- AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
- mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator(),
- getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR));
DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
? mDisappearAnimationUtilsLocked
: mDisappearAnimationUtils;
- disappearAnimationUtils.startAnimation2d(mViews,
- () -> {
+ disappearAnimationUtils.createAnimation(
+ this, 0, 200, mDisappearYTranslation, false,
+ mDisappearAnimationUtils.getInterpolator(), () -> {
+ getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR);
enableClipping(true);
if (finishRunnable != null) {
finishRunnable.run();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 28f21af..0529cdbc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -183,7 +183,7 @@
@Override
void resetState() {
mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
- mMessageAreaController.setMessage(R.string.keyguard_enter_your_password);
+ mMessageAreaController.setMessage("");
final boolean wasDisabled = mPasswordEntry.isEnabled();
mView.setPasswordEntryEnabled(true);
mView.setPasswordEntryInputEnabled(true);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 7635f919..41f9240 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -358,7 +358,7 @@
}
private void displayDefaultSecurityMessage() {
- mMessageAreaController.setMessage(R.string.keyguard_enter_your_pattern);
+ mMessageAreaController.setMessage("");
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index cc7e4f7..f7423ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -132,7 +132,6 @@
@Override
void resetState() {
mView.setPasswordEntryEnabled(true);
- mMessageAreaController.setMessage(R.string.keyguard_enter_your_pin);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 160d82a..9f4585f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -76,6 +76,12 @@
}
@Override
+ void resetState() {
+ super.resetState();
+ mMessageAreaController.setMessage("");
+ }
+
+ @Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 31f466f..087817f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -113,6 +113,22 @@
}
}
+ /** Sets a translationY value on every child view except for the media view. */
+ public void setChildrenTranslationYExcludingMediaView(float translationY) {
+ setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
+ }
+
+ /** Sets a translationY value on every view except for the views in the provided set. */
+ private void setChildrenTranslationYExcluding(float translationY, Set<View> excludedViews) {
+ for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) {
+ final View child = mStatusViewContainer.getChildAt(i);
+
+ if (!excludedViews.contains(child)) {
+ child.setTranslationY(translationY);
+ }
+ }
+ }
+
public float getChildrenAlphaExcludingSmartSpace() {
return mChildrenAlphaExcludingSmartSpace;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index af3da9f..14c9cb2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -137,6 +137,13 @@
}
/**
+ * Sets a translationY on the views on the keyguard, except on the media view.
+ */
+ public void setTranslationYExcludingMedia(float translationY) {
+ mView.setChildrenTranslationYExcludingMediaView(translationY);
+ }
+
+ /**
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 370686a..74659f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -558,7 +558,7 @@
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
- if (!mDownDetected) {
+ if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index e0d0fc2..7b98f27 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -19,6 +19,8 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -37,8 +39,14 @@
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mAnimator = new NumPadAnimator(context, getBackground().mutate(),
- attrs.getStyleAttribute());
+ Drawable background = getBackground();
+ if (background instanceof GradientDrawable) {
+ mAnimator = new NumPadAnimator(context, background.mutate(),
+ attrs.getStyleAttribute());
+ } else {
+ mAnimator = null;
+ }
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 88a0bce..5cab547 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.AttributeSet;
@@ -129,8 +131,13 @@
setContentDescription(mDigitText.getText().toString());
- mAnimator = new NumPadAnimator(context, getBackground().mutate(),
- R.style.NumPadKey, mDigitText);
+ Drawable background = getBackground();
+ if (background instanceof GradientDrawable) {
+ mAnimator = new NumPadAnimator(context, background.mutate(),
+ R.style.NumPadKey, mDigitText);
+ } else {
+ mAnimator = null;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 3a6165c..031e378 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -27,6 +27,7 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -105,6 +106,10 @@
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
log.traceEnd();
+ // Enable Looper trace points.
+ // This allows us to see Handler callbacks on traces.
+ Looper.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
+
// Set the application theme that is inherited by all services. Note that setting the
// application theme in the manifest does only work for activities. Keep this in sync with
// the theme set there.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index bf42db5..bc7a3f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -584,16 +584,18 @@
}
/**
- * Play haptic to signal udfps scanning started.
+ * If a11y touchExplorationEnabled, play haptic to signal UDFPS scanning started.
*/
@VisibleForTesting
public void playStartHaptic() {
- mVibrator.vibrate(
- Process.myUid(),
- mContext.getOpPackageName(),
- EFFECT_CLICK,
- "udfps-onStart-click",
- VIBRATION_ATTRIBUTES);
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ mVibrator.vibrate(
+ Process.myUid(),
+ mContext.getOpPackageName(),
+ EFFECT_CLICK,
+ "udfps-onStart-click",
+ VIBRATION_ATTRIBUTES);
+ }
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt
new file mode 100644
index 0000000..6615f6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt
@@ -0,0 +1,132 @@
+package com.android.systemui.broadcast
+
+import android.annotation.AnyThread
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.wakelock.WakeLock
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * SystemUI master Broadcast sender
+ *
+ * This class dispatches broadcasts on background thread to avoid synchronous call to binder. Use
+ * this class instead of calling [Context.sendBroadcast] directly.
+ */
+@SysUISingleton
+class BroadcastSender @Inject constructor(
+ private val context: Context,
+ private val wakeLockBuilder: WakeLock.Builder,
+ @Background private val bgExecutor: Executor
+) {
+
+ private val WAKE_LOCK_TAG = "SysUI:BroadcastSender"
+ private val WAKE_LOCK_SEND_REASON = "sendInBackground"
+
+ /**
+ * Sends broadcast via [Context.sendBroadcast] on background thread to avoid blocking
+ * synchronous binder call.
+ */
+ @AnyThread
+ fun sendBroadcast(intent: Intent) {
+ sendInBackground {
+ context.sendBroadcast(intent)
+ }
+ }
+
+ /**
+ * Sends broadcast via [Context.sendBroadcast] on background thread to avoid blocking
+ * synchronous binder call.
+ */
+ @AnyThread
+ fun sendBroadcast(intent: Intent, receiverPermission: String?) {
+ sendInBackground {
+ context.sendBroadcast(intent, receiverPermission)
+ }
+ }
+
+ /**
+ * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking
+ * synchronous binder call.
+ */
+ @AnyThread
+ fun sendBroadcastAsUser(intent: Intent, userHandle: UserHandle) {
+ sendInBackground {
+ context.sendBroadcastAsUser(intent, userHandle)
+ }
+ }
+
+ /**
+ * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking
+ * synchronous binder call.
+ */
+ @AnyThread
+ fun sendBroadcastAsUser(intent: Intent, userHandle: UserHandle, receiverPermission: String?) {
+ sendInBackground {
+ context.sendBroadcastAsUser(intent, userHandle, receiverPermission)
+ }
+ }
+
+ /**
+ * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking
+ * synchronous binder call.
+ */
+ @AnyThread
+ fun sendBroadcastAsUser(
+ intent: Intent,
+ userHandle: UserHandle,
+ receiverPermission: String?,
+ options: Bundle?
+ ) {
+ sendInBackground {
+ context.sendBroadcastAsUser(intent, userHandle, receiverPermission, options)
+ }
+ }
+
+ /**
+ * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking
+ * synchronous binder call.
+ */
+ @AnyThread
+ fun sendBroadcastAsUser(
+ intent: Intent,
+ userHandle: UserHandle,
+ receiverPermission: String?,
+ appOp: Int
+ ) {
+ sendInBackground {
+ context.sendBroadcastAsUser(intent, userHandle, receiverPermission, appOp)
+ }
+ }
+
+ /**
+ * Sends [Intent.ACTION_CLOSE_SYSTEM_DIALOGS] broadcast to the system.
+ */
+ @AnyThread
+ fun closeSystemDialogs() {
+ sendInBackground {
+ context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+ }
+ }
+
+ /**
+ * Dispatches parameter on background executor while holding a wakelock.
+ */
+ private fun sendInBackground(callable: () -> Unit) {
+ val broadcastWakelock = wakeLockBuilder.setTag(WAKE_LOCK_TAG)
+ .setMaxTimeout(5000)
+ .build()
+ broadcastWakelock.acquire(WAKE_LOCK_SEND_REASON)
+ bgExecutor.execute {
+ try {
+ callable.invoke()
+ } finally {
+ broadcastWakelock.release(WAKE_LOCK_SEND_REASON)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 508262d..835025b 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -16,6 +16,8 @@
package com.android.systemui.charging;
+import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -76,6 +78,16 @@
}
/**
+ * Creates a charging animation object using mostly default values for non-dozing and unknown
+ * battery level without charging number shown.
+ */
+ public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel(
+ @NonNull Context context, UiEventLogger uiEventLogger) {
+ return makeWirelessChargingAnimation(context, null,
+ UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, uiEventLogger);
+ }
+
+ /**
* Show the view for the specified duration.
*/
public void show(long delay) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 40662536..f9115b2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -17,10 +17,13 @@
package com.android.systemui.controls.management
import android.content.ComponentName
+import android.content.res.Configuration
+import android.content.res.Resources
import android.graphics.Rect
import android.os.Bundle
import android.service.controls.Control
import android.service.controls.DeviceTypes
+import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -32,7 +35,6 @@
import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.controls.ControlInterface
@@ -56,11 +58,32 @@
const val TYPE_ZONE = 0
const val TYPE_CONTROL = 1
const val TYPE_DIVIDER = 2
- }
- val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
- override fun getSpanSize(position: Int): Int {
- return if (getItemViewType(position) != TYPE_CONTROL) 2 else 1
+ /**
+ * For low-dp width screens that also employ an increased font scale, adjust the
+ * number of columns. This helps prevent text truncation on these devices.
+ *
+ */
+ @JvmStatic
+ fun findMaxColumns(res: Resources): Int {
+ var maxColumns = res.getInteger(R.integer.controls_max_columns)
+ val maxColumnsAdjustWidth =
+ res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp)
+
+ val outValue = TypedValue()
+ res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true)
+ val maxColumnsAdjustFontScale = outValue.getFloat()
+
+ val config = res.configuration
+ val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT
+ if (isPortrait &&
+ config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED &&
+ config.screenWidthDp <= maxColumnsAdjustWidth &&
+ config.fontScale >= maxColumnsAdjustFontScale) {
+ maxColumns--
+ }
+
+ return maxColumns
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 6f94943..f611c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -195,10 +195,11 @@
val margin = resources
.getDimensionPixelSize(R.dimen.controls_card_margin)
val itemDecorator = MarginItemDecorator(margin, margin)
+ val spanCount = ControlAdapter.findMaxColumns(resources)
recyclerView.apply {
this.adapter = adapter
- layoutManager = object : GridLayoutManager(recyclerView.context, 2) {
+ layoutManager = object : GridLayoutManager(recyclerView.context, spanCount) {
// This will remove from the announcement the row corresponding to the divider,
// as it's not something that should be announced.
@@ -210,7 +211,12 @@
return if (initial > 0) initial - 1 else initial
}
}.apply {
- spanSizeLookup = adapter.spanSizeLookup
+ spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (adapter?.getItemViewType(position)
+ != ControlAdapter.TYPE_CONTROL) spanCount else 1
+ }
+ }
}
addItemDecoration(itemDecorator)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
index cb67454..747bcbe 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
@@ -60,11 +60,17 @@
val margin = itemView.context.resources
.getDimensionPixelSize(R.dimen.controls_card_margin)
val itemDecorator = MarginItemDecorator(margin, margin)
+ val spanCount = ControlAdapter.findMaxColumns(itemView.resources)
recyclerView.apply {
this.adapter = controlAdapter
- layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
- spanSizeLookup = controlAdapter.spanSizeLookup
+ layoutManager = GridLayoutManager(recyclerView.context, spanCount).apply {
+ spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (adapter?.getItemViewType(position)
+ != ControlAdapter.TYPE_CONTROL) spanCount else 1
+ }
+ }
}
addItemDecoration(itemDecorator)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 5c1d8c3..e53f267 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -16,11 +16,11 @@
package com.android.systemui.controls.ui
+import android.annotation.AnyThread
import android.annotation.MainThread
import android.app.Dialog
import android.app.PendingIntent
import android.content.Context
-import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.database.ContentObserver
@@ -35,6 +35,7 @@
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -53,6 +54,7 @@
private val bgExecutor: DelayableExecutor,
@Main private val uiExecutor: DelayableExecutor,
private val activityStarter: ActivityStarter,
+ private val broadcastSender: BroadcastSender,
private val keyguardStateController: KeyguardStateController,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsMetricsLogger: ControlsMetricsLogger,
@@ -199,11 +201,12 @@
false
}
+ @AnyThread
@VisibleForTesting
fun bouncerOrRun(action: Action, authRequired: Boolean) {
if (keyguardStateController.isShowing() && authRequired) {
if (isLocked) {
- context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+ broadcastSender.closeSystemDialogs()
// pending actions will only run after the control state has been refreshed
pendingAction = action
@@ -233,7 +236,10 @@
// make sure the intent is valid before attempting to open the dialog
if (activities.isNotEmpty() && taskViewFactory.isPresent) {
taskViewFactory.get().create(context, uiExecutor, {
- dialog = DetailDialog(activityContext, it, pendingIntent, cvh).also {
+ dialog = DetailDialog(
+ activityContext, broadcastSender,
+ it, pendingIntent, cvh
+ ).also {
it.setOnDismissListener { _ -> dialog = null }
it.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 59c291c..1268250 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -25,12 +25,10 @@
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
-import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.util.Log
-import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
@@ -51,6 +49,7 @@
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.management.ControlAdapter
import com.android.systemui.controls.management.ControlsEditingActivity
import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.management.ControlsListingController
@@ -386,7 +385,7 @@
visibility = View.VISIBLE
}
- val maxColumns = findMaxColumns()
+ val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
var lastRow: ViewGroup = createRow(inflater, listView)
@@ -432,32 +431,6 @@
}
}
- /**
- * For low-dp width screens that also employ an increased font scale, adjust the
- * number of columns. This helps prevent text truncation on these devices.
- */
- private fun findMaxColumns(): Int {
- val res = activityContext.resources
- var maxColumns = res.getInteger(R.integer.controls_max_columns)
- val maxColumnsAdjustWidth =
- res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp)
-
- val outValue = TypedValue()
- res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true)
- val maxColumnsAdjustFontScale = outValue.getFloat()
-
- val config = res.configuration
- val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT
- if (isPortrait &&
- config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED &&
- config.screenWidthDp <= maxColumnsAdjustWidth &&
- config.fontScale >= maxColumnsAdjustFontScale) {
- maxColumns--
- }
-
- return maxColumns
- }
-
override fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo {
if (structures.isEmpty()) return EMPTY_STRUCTURE
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index dc3d1b5..80589a2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -33,6 +33,7 @@
import android.widget.ImageView
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastSender
import com.android.wm.shell.TaskView
/**
@@ -42,6 +43,7 @@
*/
class DetailDialog(
val activityContext: Context,
+ val broadcastSender: BroadcastSender,
val taskView: TaskView,
val pendingIntent: PendingIntent,
val cvh: ControlViewHolder
@@ -147,7 +149,7 @@
// startActivity() below is called.
removeDetailTask()
dismiss()
- context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+ broadcastSender.closeSystemDialogs()
pendingIntent.send()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 69f15e6..995df19 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -23,8 +23,6 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
@@ -39,9 +37,6 @@
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
- // The height of the area at the top of the dream overlay to allow dragging down the
- // notifications shade.
- private final int mDreamOverlayNotificationsDragAreaHeight;
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -79,9 +74,6 @@
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
- mDreamOverlayNotificationsDragAreaHeight =
- mView.getResources().getDimensionPixelSize(
- R.dimen.dream_overlay_notifications_drag_area_height);
mComplicationHostViewController = complicationHostViewController;
final View view = mComplicationHostViewController.getView();
@@ -117,11 +109,6 @@
return mView;
}
- @VisibleForTesting
- int getDreamOverlayNotificationsDragAreaHeight() {
- return mDreamOverlayNotificationsDragAreaHeight;
- }
-
private void updateBurnInOffsets() {
int burnInOffset = mMaxBurnInOffset;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index b590412..2db3de1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -16,6 +16,7 @@
package com.android.systemui.flags;
+import com.android.internal.annotations.Keep;
import com.android.systemui.R;
import java.lang.reflect.Field;
@@ -154,6 +155,7 @@
new BooleanFlag(1000, true);
// 1100 - windowing
+ @Keep
public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 831a606..510d15b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -54,6 +54,7 @@
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.monet.ColorScheme;
@@ -104,10 +105,18 @@
R.id.action4
};
+ // Buttons to show in small player when using semantic actions
+ private static final List<Integer> SEMANTIC_ACTION_IDS = List.of(
+ R.id.actionPlayPause,
+ R.id.actionPrev,
+ R.id.actionNext
+ );
+
private final SeekBarViewModel mSeekBarViewModel;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
private final ActivityStarter mActivityStarter;
+ private final BroadcastSender mBroadcastSender;
private Context mContext;
private MediaViewHolder mMediaViewHolder;
@@ -142,14 +151,16 @@
*/
@Inject
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
- ActivityStarter activityStarter, MediaViewController mediaViewController,
- SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
+ ActivityStarter activityStarter, BroadcastSender broadcastSender,
+ MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel,
+ Lazy<MediaDataManager> lazyMediaDataManager,
MediaOutputDialogFactory mediaOutputDialogFactory,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
+ mBroadcastSender = broadcastSender;
mSeekBarViewModel = seekBarViewModel;
mMediaViewController = mediaViewController;
mMediaDataManagerLazy = lazyMediaDataManager;
@@ -501,11 +512,11 @@
MediaButton semanticActions = data.getSemanticActions();
actionIcons = new ArrayList<MediaAction>();
- actionIcons.add(semanticActions.getStartCustom());
+ actionIcons.add(semanticActions.getCustom0());
actionIcons.add(semanticActions.getPrevOrCustom());
actionIcons.add(semanticActions.getPlayOrPause());
actionIcons.add(semanticActions.getNextOrCustom());
- actionIcons.add(semanticActions.getEndCustom());
+ actionIcons.add(semanticActions.getCustom1());
actionsWhenCollapsed = new ArrayList<Integer>();
actionsWhenCollapsed.add(1);
@@ -562,6 +573,9 @@
/** Bind elements specific to PlayerSessionViewHolder */
private void bindSessionPlayer(@NonNull MediaData data, String key) {
+ ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
+
// Default colors
int surfaceColor = mBackgroundColor;
int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
@@ -575,25 +589,6 @@
int textTertiary = com.android.settingslib.Utils.getColorAttr(mContext,
com.android.internal.R.attr.textColorTertiary).getDefaultColor();
- // App icon - use launcher icon
- ImageView appIconView = mMediaViewHolder.getAppIcon();
- appIconView.clearColorFilter();
- try {
- Drawable icon = mContext.getPackageManager().getApplicationIcon(
- data.getPackageName());
- appIconView.setImageDrawable(icon);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
- // Fall back to notification icon
- if (data.getAppIcon() != null) {
- appIconView.setImageIcon(data.getAppIcon());
- } else {
- appIconView.setImageResource(R.drawable.ic_music_note);
- }
- int color = mContext.getColor(R.color.material_dynamic_secondary10);
- appIconView.setColorFilter(color);
- }
-
// Album art
ColorScheme colorScheme = null;
ImageView albumView = mMediaViewHolder.getAlbumView();
@@ -640,6 +635,25 @@
ColorStateList.valueOf(surfaceColor));
mMediaViewHolder.getPlayer().setBackgroundTintList(bgColorList);
+ // App icon - use notification icon
+ ImageView appIconView = mMediaViewHolder.getAppIcon();
+ appIconView.clearColorFilter();
+ if (data.getAppIcon() != null && !data.getResumption()) {
+ appIconView.setImageIcon(data.getAppIcon());
+ appIconView.setColorFilter(accentPrimary);
+ } else {
+ // Resume players use launcher icon
+ appIconView.setColorFilter(getGrayscaleFilter());
+ try {
+ Drawable icon = mContext.getPackageManager().getApplicationIcon(
+ data.getPackageName());
+ appIconView.setImageDrawable(icon);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
+ appIconView.setImageResource(R.drawable.ic_music_note);
+ }
+ }
+
// Metadata text
mMediaViewHolder.getTitleText().setTextColor(textPrimary);
mMediaViewHolder.getArtistText().setTextColor(textSecondary);
@@ -660,26 +674,68 @@
// Media action buttons
MediaButton semanticActions = data.getSemanticActions();
+ PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
+ ImageButton[] genericButtons = new ImageButton[]{
+ sessionHolder.getAction0(),
+ sessionHolder.getAction1(),
+ sessionHolder.getAction2(),
+ sessionHolder.getAction3(),
+ sessionHolder.getAction4()};
+
+ ImageButton[] semanticButtons = new ImageButton[]{
+ sessionHolder.getActionPlayPause(),
+ sessionHolder.getActionNext(),
+ sessionHolder.getActionPrev()};
+
if (semanticActions != null) {
- PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
+ // Hide all the generic buttons
+ for (ImageButton b: genericButtons) {
+ setVisibleAndAlpha(collapsedSet, b.getId(), false);
+ setVisibleAndAlpha(expandedSet, b.getId(), false);
+ }
// Play/pause button has a background
sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(),
- ColorStateList.valueOf(textPrimaryInverse));
+ ColorStateList.valueOf(textPrimaryInverse), collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
- textColorList);
+ textColorList, collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
- textColorList);
- setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(),
- textColorList);
- setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(),
- textColorList);
+ textColorList, collapsedSet, expandedSet, true);
+ setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(),
+ textColorList, collapsedSet, expandedSet, false);
+ setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(),
+ textColorList, collapsedSet, expandedSet, false);
} else {
- Log.w(TAG, "Using semantic player, but did not get buttons");
+ // Hide all the semantic buttons
+ for (int id : SEMANTIC_ACTION_IDS) {
+ setVisibleAndAlpha(collapsedSet, id, false);
+ setVisibleAndAlpha(expandedSet, id, false);
+ }
+
+ // Set all the generic buttons
+ List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+ List<MediaAction> actions = data.getActions();
+ int i = 0;
+ for (; i < actions.size(); i++) {
+ boolean showInCompact = actionsWhenCollapsed.contains(i);
+ setSemanticButton(genericButtons[i], actions.get(i), textColorList, collapsedSet,
+ expandedSet, showInCompact);
+ }
+ for (; i < 5; i++) {
+ // Hide any unused buttons
+ setSemanticButton(genericButtons[i], null, textColorList, collapsedSet,
+ expandedSet, false);
+ }
}
+ // If disabled, set progress bar to INVISIBLE instead of GONE so layout weights still work
+ boolean seekbarEnabled = mSeekBarViewModel.getEnabled();
+ expandedSet.setVisibility(R.id.media_progress_bar,
+ seekbarEnabled ? ConstraintSet.VISIBLE : ConstraintSet.INVISIBLE);
+ expandedSet.setAlpha(R.id.media_progress_bar, seekbarEnabled ? 1.0f : 0.0f);
+
// Long press buttons
mMediaViewHolder.getLongPressText().setTextColor(textColorList);
mMediaViewHolder.getSettingsText().setTextColor(textColorList);
@@ -688,11 +744,11 @@
mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList);
mMediaViewHolder.getDismissText().setTextColor(textColorList);
mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
-
}
private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
- ColorStateList fgColor) {
+ ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet,
+ boolean showInCompact) {
button.setImageTintList(fgColor);
if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon());
@@ -716,6 +772,9 @@
button.setContentDescription(null);
button.setEnabled(false);
}
+
+ setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact);
+ setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
}
@Nullable
@@ -899,7 +958,7 @@
// Dismiss the card Smartspace data through Smartspace trampoline activity.
mContext.startActivity(dismissIntent);
} else {
- mContext.sendBroadcast(dismissIntent);
+ mBroadcastSender.sendBroadcast(dismissIntent);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 500e82e..4cf6291 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -149,11 +149,11 @@
/**
* First custom action space
*/
- var startCustom: MediaAction? = null,
+ var custom0: MediaAction? = null,
/**
- * Last custom action space
+ * Second custom action space
*/
- var endCustom: MediaAction? = null
+ var custom1: MediaAction? = null
)
/** State of a media action. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index ae5c1f2..de44a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -21,6 +21,7 @@
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -56,6 +57,7 @@
class MediaDataFilter @Inject constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val broadcastSender: BroadcastSender,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@Main private val executor: Executor,
private val systemClock: SystemClock
@@ -249,7 +251,7 @@
// Dismiss the card Smartspace data through Smartspace trampoline activity.
context.startActivity(dismissIntent)
} else {
- context.sendBroadcast(dismissIntent)
+ broadcastSender.sendBroadcast(dismissIntent)
}
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index fab06c2..9e14fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -752,8 +752,8 @@
null
}
- actions.startCustom = customActions[customIdx++]
- actions.endCustom = customActions[customIdx++]
+ actions.custom0 = customActions[customIdx++]
+ actions.custom1 = customActions[customIdx++]
}
return actions
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 18b6699..7a4dee2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -292,6 +292,18 @@
}
/**
+ * Returns the amount of translationY of the media container, during the current guided
+ * transformation, if running. If there is no guided transformation running, it will return 0.
+ */
+ fun getGuidedTransformationTranslationY(): Int {
+ if (!isCurrentlyInGuidedTransformation()) {
+ return -1
+ }
+ val startHost = getHost(previousLocation) ?: return 0
+ return targetBounds.top - startHost.currentBounds.top
+ }
+
+ /**
* Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
* we wouldn't want to transition in that case.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 77873e8..3860409 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -54,7 +54,7 @@
private int mUid;
private IMediaProjectionManager mService;
- private SystemUIDialog mDialog;
+ private AlertDialog mDialog;
@Override
public void onCreate(Bundle icicle) {
@@ -141,13 +141,18 @@
dialogTitle = getString(R.string.media_projection_dialog_title, appName);
}
- mDialog = new SystemUIDialog(this);
- mDialog.setTitle(dialogTitle);
- mDialog.setIcon(R.drawable.ic_media_projection_permission);
- mDialog.setMessage(dialogText);
- mDialog.setPositiveButton(R.string.media_projection_action_text, this);
- mDialog.setNeutralButton(android.R.string.cancel, this);
- mDialog.setOnCancelListener(this);
+ mDialog = new AlertDialog.Builder(this, R.style.Theme_SystemUI_Dialog)
+ .setTitle(dialogTitle)
+ .setIcon(R.drawable.ic_media_projection_permission)
+ .setMessage(dialogText)
+ .setPositiveButton(R.string.media_projection_action_text, this)
+ .setNeutralButton(android.R.string.cancel, this)
+ .setOnCancelListener(this)
+ .create();
+
+ SystemUIDialog.registerDismissListener(mDialog);
+ SystemUIDialog.applyFlags(mDialog);
+ SystemUIDialog.setDialogSize(mDialog);
mDialog.create();
mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
@@ -186,7 +191,7 @@
private Intent getMediaProjectionIntent(int uid, String packageName)
throws RemoteException {
IMediaProjection projection = mService.createProjection(uid, packageName,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
Intent intent = new Intent();
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
return intent;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
index e57b247..5f60696 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
@@ -60,12 +60,24 @@
val settings = itemView.requireViewById<View>(R.id.settings)
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
+ // Action Buttons
+ val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
+ val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
+ val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
+ val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
+ val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
+
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(seamless)
it.registerLightSource(cancel)
it.registerLightSource(dismiss)
it.registerLightSource(settings)
+ it.registerLightSource(action0)
+ it.registerLightSource(action1)
+ it.registerLightSource(action2)
+ it.registerLightSource(action3)
+ it.registerLightSource(action4)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt
index 87d2cff..6928ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt
@@ -31,16 +31,12 @@
val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause)
val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext)
val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev)
- val actionStart = itemView.requireViewById<ImageButton>(R.id.actionStart)
- val actionEnd = itemView.requireViewById<ImageButton>(R.id.actionEnd)
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(actionPlayPause)
it.registerLightSource(actionNext)
it.registerLightSource(actionPrev)
- it.registerLightSource(actionStart)
- it.registerLightSource(actionEnd)
}
}
@@ -49,8 +45,11 @@
R.id.actionPlayPause -> actionPlayPause
R.id.actionNext -> actionNext
R.id.actionPrev -> actionPrev
- R.id.actionStart -> actionStart
- R.id.actionEnd -> actionEnd
+ R.id.action0 -> action0
+ R.id.action1 -> action1
+ R.id.action2 -> action2
+ R.id.action3 -> action3
+ R.id.action4 -> action4
else -> {
throw IllegalArgumentException()
}
@@ -90,8 +89,11 @@
R.id.actionPlayPause,
R.id.actionNext,
R.id.actionPrev,
- R.id.actionStart,
- R.id.actionEnd,
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4,
R.id.icon
)
val gutsIds = setOf(
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 20b2d4a..dd3fa89 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -33,23 +33,6 @@
override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
override val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
- // Action Buttons
- val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
- val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
- val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
- val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
- val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
-
- init {
- (player.background as IlluminationDrawable).let {
- it.registerLightSource(action0)
- it.registerLightSource(action1)
- it.registerLightSource(action2)
- it.registerLightSource(action3)
- it.registerLightSource(action4)
- }
- }
-
override fun getAction(id: Int): ImageButton {
return when (id) {
R.id.action0 -> action0
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index cf997055..57701ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -50,25 +50,46 @@
.getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding)
}
+ init {
+ val seekBarProgressWavelength = holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength).toFloat()
+ val seekBarProgressAmplitude = holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude).toFloat()
+ val seekBarProgressPhase = holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase).toFloat()
+ val seekBarProgressStrokeWidth = holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width).toFloat()
+ val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress
+ progressDrawable?.let {
+ it.waveLength = seekBarProgressWavelength
+ it.lineAmplitude = seekBarProgressAmplitude
+ it.phaseSpeed = seekBarProgressPhase
+ it.strokeWidth = seekBarProgressStrokeWidth
+ }
+ }
+
/** Updates seek bar views when the data model changes. */
@UiThread
override fun onChanged(data: SeekBarViewModel.Progress) {
+ val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress
if (!data.enabled) {
if (holder.seekBar.maxHeight != seekBarDisabledHeight) {
holder.seekBar.maxHeight = seekBarDisabledHeight
setVerticalPadding(seekBarDisabledVerticalPadding)
}
- holder.seekBar.setEnabled(false)
- holder.seekBar.getThumb().setAlpha(0)
- holder.seekBar.setProgress(0)
- holder.elapsedTimeView?.setText("")
- holder.totalTimeView?.setText("")
+ holder.seekBar.isEnabled = false
+ progressDrawable?.animate = false
+ holder.seekBar.thumb.alpha = 0
+ holder.seekBar.progress = 0
+ holder.elapsedTimeView?.text = ""
+ holder.totalTimeView?.text = ""
holder.seekBar.contentDescription = ""
return
}
- holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
- holder.seekBar.setEnabled(data.seekAvailable)
+ holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
+ holder.seekBar.isEnabled = data.seekAvailable
+ progressDrawable?.animate = data.playing
if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
holder.seekBar.maxHeight = seekBarEnabledMaxHeight
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 9cf9c48..49cd161 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -31,6 +31,7 @@
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.util.concurrency.RepeatableExecutor
import javax.inject.Inject
@@ -73,7 +74,7 @@
class SeekBarViewModel @Inject constructor(
@Background private val bgExecutor: RepeatableExecutor
) {
- private var _data = Progress(false, false, null, 0)
+ private var _data = Progress(false, false, false, null, 0)
set(value) {
field = value
_progress.postValue(value)
@@ -131,6 +132,8 @@
lateinit var logSmartspaceClick: () -> Unit
+ fun getEnabled() = _data.enabled
+
/**
* Event indicating that the user has started interacting with the seek bar.
*/
@@ -192,10 +195,12 @@
val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
val position = playbackState?.position?.toInt()
val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
+ val playing = NotificationMediaManager
+ .isPlayingState(playbackState?.state ?: PlaybackState.STATE_NONE)
val enabled = if (playbackState == null ||
playbackState?.getState() == PlaybackState.STATE_NONE ||
(duration <= 0)) false else true
- _data = Progress(enabled, seekAvailable, position, duration)
+ _data = Progress(enabled, seekAvailable, playing, position, duration)
checkIfPollingNeeded()
}
@@ -412,6 +417,7 @@
data class Progress(
val enabled: Boolean,
val seekAvailable: Boolean,
+ val playing: Boolean,
val elapsedTime: Int?,
val duration: Int
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
new file mode 100644
index 0000000..f1058e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
@@ -0,0 +1,184 @@
+package com.android.systemui.media
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.res.ColorStateList
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.os.SystemClock
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.Interpolators
+import kotlin.math.abs
+import kotlin.math.cos
+
+private const val TAG = "Squiggly"
+
+private const val TWO_PI = (Math.PI * 2f).toFloat()
+@VisibleForTesting
+internal const val DISABLED_ALPHA = 77
+
+class SquigglyProgress : Drawable() {
+
+ private val wavePaint = Paint()
+ private val linePaint = Paint()
+ private val path = Path()
+ private var heightFraction = 0f
+ private var heightAnimator: ValueAnimator? = null
+ private var phaseOffset = 0f
+ private var lastFrameTime = -1L
+
+ // Horizontal length of the sine wave
+ var waveLength = 0f
+ // Height of each peak of the sine wave
+ var lineAmplitude = 0f
+ // Line speed in px per second
+ var phaseSpeed = 0f
+ // Progress stroke width, both for wave and solid line
+ var strokeWidth = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ wavePaint.strokeWidth = value
+ linePaint.strokeWidth = value
+ }
+
+ init {
+ wavePaint.strokeCap = Paint.Cap.ROUND
+ linePaint.strokeCap = Paint.Cap.ROUND
+ linePaint.style = Paint.Style.STROKE
+ wavePaint.style = Paint.Style.STROKE
+ linePaint.alpha = DISABLED_ALPHA
+ }
+
+ var animate: Boolean = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ if (field) {
+ lastFrameTime = SystemClock.uptimeMillis()
+ }
+ heightAnimator?.cancel()
+ heightAnimator = ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
+ if (animate) {
+ startDelay = 60
+ duration = 800
+ interpolator = Interpolators.EMPHASIZED_DECELERATE
+ } else {
+ duration = 550
+ interpolator = Interpolators.STANDARD_DECELERATE
+ }
+ addUpdateListener {
+ heightFraction = it.animatedValue as Float
+ invalidateSelf()
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ heightAnimator = null
+ }
+ })
+ start()
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ if (animate) {
+ invalidateSelf()
+ val now = SystemClock.uptimeMillis()
+ phaseOffset -= (now - lastFrameTime) / 1000f * phaseSpeed
+ phaseOffset %= waveLength
+ lastFrameTime = now
+ }
+
+ val totalProgressPx = (bounds.width() * (level / 10_000f))
+ canvas.save()
+ canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat())
+ // Clip drawing, so we stop at the thumb
+ canvas.clipRect(
+ 0f,
+ -lineAmplitude - strokeWidth,
+ totalProgressPx,
+ lineAmplitude + strokeWidth)
+
+ // The squiggly line
+ val start = phaseOffset
+ var currentX = start
+ var waveSign = 1f
+ path.rewind()
+ path.moveTo(start, lineAmplitude * heightFraction)
+ while (currentX < totalProgressPx) {
+ val nextX = currentX + waveLength / 2f
+ val nextWaveSign = waveSign * -1
+ path.cubicTo(
+ currentX + waveLength / 4f, lineAmplitude * waveSign * heightFraction,
+ nextX - waveLength / 4f, lineAmplitude * nextWaveSign * heightFraction,
+ nextX, lineAmplitude * nextWaveSign * heightFraction)
+ currentX = nextX
+ waveSign = nextWaveSign
+ }
+ wavePaint.style = Paint.Style.STROKE
+ canvas.drawPath(path, wavePaint)
+ canvas.restore()
+
+ // Draw round line cap at the beginning of the wave
+ val startAmp = cos(abs(phaseOffset) / waveLength * TWO_PI)
+ val p = Paint()
+ p.color = Color.WHITE
+ canvas.drawPoint(
+ bounds.left.toFloat(),
+ bounds.centerY() + startAmp * lineAmplitude * heightFraction,
+ wavePaint)
+
+ // Draw continuous line, to the right of the thumb
+ canvas.drawLine(
+ bounds.left.toFloat() + totalProgressPx,
+ bounds.centerY().toFloat(),
+ bounds.width().toFloat(),
+ bounds.centerY().toFloat(),
+ linePaint)
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.TRANSLUCENT
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ wavePaint.colorFilter = colorFilter
+ linePaint.colorFilter = colorFilter
+ }
+
+ override fun setAlpha(alpha: Int) {
+ wavePaint.alpha = alpha
+ linePaint.alpha = (DISABLED_ALPHA * (alpha / 255f)).toInt()
+ }
+
+ override fun getAlpha(): Int {
+ return wavePaint.alpha
+ }
+
+ override fun setTint(tintColor: Int) {
+ wavePaint.color = tintColor
+ linePaint.color = tintColor
+ }
+
+ override fun onLevelChange(level: Int): Boolean {
+ return animate
+ }
+
+ override fun setTintList(tint: ColorStateList?) {
+ if (tint == null) {
+ return
+ }
+ wavePaint.color = tint.defaultColor
+ linePaint.color = tint.defaultColor
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 355c69f..a8141c0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -21,7 +21,6 @@
import android.app.WallpaperColors;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -55,6 +54,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.statusbar.phone.SystemUIDialog;
/**
@@ -71,6 +71,7 @@
final Context mContext;
final MediaOutputController mMediaOutputController;
+ final BroadcastSender mBroadcastSender;
@VisibleForTesting
View mDialogView;
@@ -98,11 +99,13 @@
}
};
- public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
+ public MediaOutputBaseDialog(Context context, BroadcastSender broadcastSender,
+ MediaOutputController mediaOutputController) {
super(context, R.style.Theme_SystemUI_Dialog_Media);
// Save the context that is wrapped with our theme.
mContext = getContext();
+ mBroadcastSender = broadcastSender;
mMediaOutputController = mediaOutputController;
mLayoutManager = new LinearLayoutManager(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize(
@@ -152,7 +155,7 @@
dismiss();
});
mAppButton.setOnClickListener(v -> {
- mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ mBroadcastSender.closeSystemDialogs();
if (mMediaOutputController.getAppLaunchIntent() != null) {
mContext.startActivity(mMediaOutputController.getAppLaunchIntent());
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 0c202e0..0b6c68d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -53,7 +53,6 @@
import androidx.mediarouter.media.MediaRouter;
import androidx.mediarouter.media.MediaRouterParams;
-import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -70,7 +69,6 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.phone.ShadeController;
import java.util.ArrayList;
import java.util.Collection;
@@ -95,12 +93,9 @@
private final String mPackageName;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
- private final LocalBluetoothManager mLocalBluetoothManager;
- private final ShadeController mShadeController;
private final ActivityStarter mActivityStarter;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
- private final boolean mAboveStatusbar;
private final CommonNotifCollection mNotifCollection;
@VisibleForTesting
final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@@ -114,7 +109,6 @@
LocalMediaManager mLocalMediaManager;
private MediaOutputMetricLogger mMetricLogger;
- private UiEventLogger mUiEventLogger;
private int mColorActiveItem;
private int mColorInactiveItem;
@@ -124,23 +118,19 @@
@Inject
public MediaOutputController(@NonNull Context context, String packageName,
- boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
- lbm, ShadeController shadeController, ActivityStarter starter,
- CommonNotifCollection notifCollection, UiEventLogger uiEventLogger,
+ MediaSessionManager mediaSessionManager, LocalBluetoothManager
+ lbm, ActivityStarter starter,
+ CommonNotifCollection notifCollection,
DialogLaunchAnimator dialogLaunchAnimator,
Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
- mLocalBluetoothManager = lbm;
- mShadeController = shadeController;
mActivityStarter = starter;
- mAboveStatusbar = aboveStatusbar;
mNotifCollection = notifCollection;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
- mUiEventLogger = uiEventLogger;
mDialogLaunchAnimator = dialogLaunchAnimator;
mNearbyMediaDevicesManager = nearbyMediaDevicesManagerOptional.orElse(null);
mColorActiveItem = Utils.getColorStateListDefaultColor(mContext,
@@ -630,18 +620,6 @@
mActivityStarter.startActivity(launchIntent, true, controller);
}
- void launchMediaOutputGroupDialog(View mediaOutputDialog) {
- // We show the output group dialog from the output dialog.
- MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
- mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
- mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager));
-
- MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
- controller);
- mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
- }
-
boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
final List<String> features = device.getFeatures();
return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 7696a1f..7834ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -28,6 +28,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
/**
@@ -37,9 +38,9 @@
public class MediaOutputDialog extends MediaOutputBaseDialog {
final UiEventLogger mUiEventLogger;
- MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController
- mediaOutputController, UiEventLogger uiEventLogger) {
- super(context, mediaOutputController);
+ MediaOutputDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender,
+ MediaOutputController mediaOutputController, UiEventLogger uiEventLogger) {
+ super(context, broadcastSender, mediaOutputController);
mUiEventLogger = uiEventLogger;
mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index a7e5480..0d7d60a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -22,10 +22,10 @@
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.phone.ShadeController
import java.util.Optional
import javax.inject.Inject
@@ -36,8 +36,8 @@
private val context: Context,
private val mediaSessionManager: MediaSessionManager,
private val lbm: LocalBluetoothManager?,
- private val shadeController: ShadeController,
private val starter: ActivityStarter,
+ private val broadcastSender: BroadcastSender,
private val notifCollection: CommonNotifCollection,
private val uiEventLogger: UiEventLogger,
private val dialogLaunchAnimator: DialogLaunchAnimator,
@@ -52,10 +52,11 @@
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = MediaOutputController(context, packageName, aboveStatusBar,
- mediaSessionManager, lbm, shadeController, starter, notifCollection,
- uiEventLogger, dialogLaunchAnimator, nearbyMediaDevicesManagerOptional)
- val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger)
+ val controller = MediaOutputController(context, packageName,
+ mediaSessionManager, lbm, starter, notifCollection,
+ dialogLaunchAnimator, nearbyMediaDevicesManagerOptional)
+ val dialog =
+ MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
// Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
index f1c6601..bb3f969 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -25,6 +25,7 @@
import androidx.core.graphics.drawable.IconCompat;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
/**
* Dialog for media output group.
@@ -32,9 +33,9 @@
// TODO(b/203073091): Remove this class once group logic been implemented.
public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
- MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController
- mediaOutputController) {
- super(context, mediaOutputController);
+ MediaOutputGroupDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender,
+ MediaOutputController mediaOutputController) {
+ super(context, broadcastSender, mediaOutputController);
mMediaOutputController.resetGroupMediaDevices();
mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index b4e20fd..2ac34b2 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -60,6 +60,7 @@
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -160,17 +161,20 @@
@VisibleForTesting SystemUIDialog mUsbHighTempDialog;
private BatteryStateSnapshot mCurrentBatterySnapshot;
private ActivityStarter mActivityStarter;
+ private final BroadcastSender mBroadcastSender;
/**
*/
@Inject
- public PowerNotificationWarnings(Context context, ActivityStarter activityStarter) {
+ public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
+ BroadcastSender broadcastSender) {
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mKeyguard = mContext.getSystemService(KeyguardManager.class);
mReceiver.init();
mActivityStarter = activityStarter;
+ mBroadcastSender = broadcastSender;
mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
}
@@ -258,7 +262,7 @@
protected void showWarningNotification() {
if (showSevereLowBatteryDialog()) {
- mContext.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG)
+ mBroadcastSender.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG)
.setPackage(mContext.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND));
@@ -716,9 +720,9 @@
mSaverConfirmation.dismiss();
}
// Also close the notification shade, if it's open.
- mContext.sendBroadcast(
+ mBroadcastSender.sendBroadcast(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND));
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND));
final Uri uri = Uri.parse(getURL());
Context context = widget.getContext();
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index e26c768..8000bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -160,14 +160,15 @@
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
*/
public boolean isEnabledForLockScreenButton() {
- return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton;
+ return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton
+ && isActivityCallable(mIntent);
}
/**
* Returns true if quick settings entry point for QR Code Scanner is to be enabled.
*/
public boolean isEnabledForQuickSettings() {
- return mIntent != null;
+ return mIntent != null && isActivityCallable(mIntent);
}
/**
@@ -278,7 +279,7 @@
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
}
- if (isActivityCallable(intent)) {
+ if (isActivityAvailable(intent)) {
mQRCodeScannerActivity = qrCodeScannerActivity;
mComponentName = componentName;
mIntent = intent;
@@ -293,7 +294,7 @@
}
}
- private boolean isActivityCallable(Intent intent) {
+ private boolean isActivityAvailable(Intent intent) {
// Our intent should always be explicit and should have a component set
if (intent.getComponent() == null) return false;
@@ -307,6 +308,17 @@
flags).isEmpty();
}
+ private boolean isActivityCallable(Intent intent) {
+ // Our intent should always be explicit and should have a component set
+ if (intent.getComponent() == null) return false;
+
+ int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ return !mContext.getPackageManager().queryIntentActivities(intent,
+ flags).isEmpty();
+ }
+
private void unregisterUserChangeObservers() {
mUserTracker.removeCallback(mUserChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 32515a2..4cacbba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -15,26 +15,22 @@
*/
package com.android.systemui.qs.external;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.statusbar.StatusBarIcon;
@@ -42,6 +38,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -51,6 +48,7 @@
import java.util.Objects;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Runs the day-to-day operations of which tiles should be bound and when.
@@ -64,11 +62,12 @@
private final ArrayMap<ComponentName, CustomTile> mTiles = new ArrayMap<>();
private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>();
private final Context mContext;
- private final Handler mHandler;
private final Handler mMainHandler;
+ private final Provider<Handler> mHandlerProvider;
private final QSTileHost mHost;
private final KeyguardStateController mKeyguardStateController;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final CommandQueue mCommandQueue;
private final UserTracker mUserTracker;
private int mMaxBound = DEFAULT_MAX_BOUND;
@@ -76,23 +75,20 @@
@Inject
public TileServices(
QSTileHost host,
- @Main Looper looper,
+ @Main Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ CommandQueue commandQueue) {
mHost = host;
mKeyguardStateController = keyguardStateController;
mContext = mHost.getContext();
mBroadcastDispatcher = broadcastDispatcher;
- mHandler = new Handler(looper);
- mMainHandler = new Handler(Looper.getMainLooper());
+ mHandlerProvider = handlerProvider;
+ mMainHandler = mHandlerProvider.get();
mUserTracker = userTracker;
- mBroadcastDispatcher.registerReceiver(
- mRequestListeningReceiver,
- new IntentFilter(TileService.ACTION_REQUEST_LISTENING),
- null, // Use the default Executor
- UserHandle.ALL
- );
+ mCommandQueue = commandQueue;
+ mCommandQueue.addCallback(mRequestListeningCallback);
}
public Context getContext() {
@@ -118,7 +114,7 @@
protected TileServiceManager onCreateTileService(ComponentName component,
BroadcastDispatcher broadcastDispatcher) {
- return new TileServiceManager(this, mHandler, component,
+ return new TileServiceManager(this, mHandlerProvider.get(), component,
broadcastDispatcher, mUserTracker);
}
@@ -354,21 +350,14 @@
public void destroy() {
synchronized (mServices) {
mServices.values().forEach(service -> service.handleDestroy());
- mBroadcastDispatcher.unregisterReceiver(mRequestListeningReceiver);
}
+ mCommandQueue.removeCallback(mRequestListeningCallback);
}
- private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
+ private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() {
@Override
- public void onReceive(Context context, Intent intent) {
- if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
- try {
- ComponentName c = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME);
- requestListening(c);
- } catch (ClassCastException ex) {
- Log.e(TAG, "Bad component name", ex);
- }
- }
+ public void requestTileServiceListeningState(@NonNull ComponentName componentName) {
+ mMainHandler.post(() -> requestListening(componentName));
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index e1d2070..8bad2de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -47,9 +47,6 @@
public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> {
private static final String TAG = "InternetAdapter";
- private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
- private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
- private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
private final InternetDialogController mInternetDialogController;
@Nullable
@@ -169,11 +166,10 @@
}
mWifiListLayout.setOnClickListener(v -> {
if (wifiEntry.shouldEditBeforeConnect()) {
- final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+ final Intent intent = WifiUtils.getWifiDialogIntent(wifiEntry.getKey(),
+ true /* connectForCaller */);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey());
- intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false);
mContext.startActivity(intent);
}
mInternetDialogController.connect(wifiEntry);
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 b3bc3be..b322cbf 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
@@ -112,7 +112,6 @@
"android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
- private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
public static final int NO_CELL_DATA_TYPE_ICON = 0;
private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
@@ -853,8 +852,8 @@
}
if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
- final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
- .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
+ final Intent intent = WifiUtils.getWifiDialogIntent(mWifiEntry.getKey(),
+ true /* connectForCaller */);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivityStarter.startActivity(intent, false /* dismissShade */);
} else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 50765f2..009d4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -88,10 +88,12 @@
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
+import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
@@ -247,6 +249,7 @@
private final ImageExporter mImageExporter;
private final Executor mMainExecutor;
private final ExecutorService mBgExecutor;
+ private final BroadcastSender mBroadcastSender;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
@@ -271,7 +274,6 @@
private String mPackageName = "";
private BroadcastReceiver mCopyBroadcastReceiver;
-
/** Tracks config changes that require re-creating UI */
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_ORIENTATION
@@ -293,7 +295,8 @@
ScrollCaptureController scrollCaptureController,
LongScreenshotData longScreenshotHolder,
ActivityManager activityManager,
- TimeoutHandler timeoutHandler) {
+ TimeoutHandler timeoutHandler,
+ BroadcastSender broadcastSender) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
mScrollCaptureClient = scrollCaptureClient;
@@ -304,6 +307,7 @@
mLongScreenshotHolder = longScreenshotHolder;
mIsLowRamDevice = activityManager.isLowRamDevice();
mBgExecutor = Executors.newSingleThreadExecutor();
+ mBroadcastSender = broadcastSender;
mScreenshotHandler = timeoutHandler;
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
@@ -355,8 +359,10 @@
ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
}
+ @MainThread
void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
RequestCallback requestCallback) {
+ Assert.isMainThread();
mCurrentRequestCallback = requestCallback;
DisplayMetrics displayMetrics = new DisplayMetrics();
getDefaultDisplay().getRealMetrics(displayMetrics);
@@ -365,11 +371,12 @@
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
}
+ @MainThread
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
Consumer<Uri> finisher, RequestCallback requestCallback) {
// TODO: use task Id, userId, topComponent for smart handler
-
+ Assert.isMainThread();
if (screenshot == null) {
Log.e(TAG, "Got null bitmap from screenshot message");
mNotificationsController.notifyScreenshotError(
@@ -392,8 +399,10 @@
/**
* Displays a screenshot selector
*/
+ @MainThread
void takeScreenshotPartial(ComponentName topComponent,
final Consumer<Uri> finisher, RequestCallback requestCallback) {
+ Assert.isMainThread();
mScreenshotView.reset();
mCurrentRequestCallback = requestCallback;
@@ -517,7 +526,7 @@
saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);
- mContext.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+ mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
ClipboardOverlayController.SELF_PERMISSION);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 5932a64..d9a98b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -163,6 +163,7 @@
private static final int MSG_MEDIA_TRANSFER_RECEIVER_STATE = 65 << MSG_SHIFT;
private static final int MSG_REGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 66 << MSG_SHIFT;
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
+ private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -433,6 +434,11 @@
default void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {}
/**
+ * @see IStatusBar#requestTileServiceListeningState
+ */
+ default void requestTileServiceListeningState(@NonNull ComponentName componentName) {}
+
+ /**
* @see IStatusBar#requestAddTile
*/
default void requestAddTile(
@@ -1190,6 +1196,12 @@
}
@Override
+ public void requestTileServiceListeningState(@NonNull ComponentName componentName) {
+ mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_LISTENING_STATE, componentName)
+ .sendToTarget();
+ }
+
+ @Override
public void requestAddTile(
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@@ -1686,6 +1698,12 @@
mCallbacks.get(i).unregisterNearbyMediaDevicesProvider(provider);
}
break;
+ case MSG_TILE_SERVICE_REQUEST_LISTENING_STATE:
+ ComponentName component = (ComponentName) msg.obj;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).requestTileServiceListeningState(component);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 1ab0345..ab4d0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -406,6 +406,7 @@
mediaHierarchyManager.setTransitionToFullShadeAmount(field)
transitionToShadeAmountCommon(field)
+ transitionToShadeAmountKeyguard(field)
}
}
}
@@ -420,11 +421,6 @@
val scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
scrimController.setTransitionToFullShadeProgress(scrimProgress)
- // Fade out all content only visible on the lockscreen
- val npvcProgress =
- MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
- notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - npvcProgress)
-
if (depthControllerTransitionDistance > 0) {
val depthProgress =
MathUtils.saturate(dragDownAmount / depthControllerTransitionDistance)
@@ -438,6 +434,22 @@
centralSurfaces.setTransitionToFullShadeProgress(statusBarProgress)
}
+ private fun transitionToShadeAmountKeyguard(dragDownAmount: Float) {
+ // Fade out all content only visible on the lockscreen
+ val keyguardAlphaProgress =
+ MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
+ val keyguardAlpha = 1f - keyguardAlphaProgress
+ val keyguardTranslationY = if (useSplitShade) {
+ // On split-shade, the translationY of the keyguard should stay in sync with the
+ // translation of media.
+ mediaHierarchyManager.getGuidedTransformationTranslationY()
+ } else {
+ 0
+ }
+ notificationPanelController
+ .setKeyguardTransitionProgress(keyguardAlpha, keyguardTranslationY)
+ }
+
private fun setDragDownAmountAnimated(
target: Float,
delay: Long = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 267ee6d..a0388de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -259,6 +259,7 @@
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
keyguardAnimator = null
+ wakeAndUnlockBlurRadius = 0f
scheduleUpdate()
}
})
@@ -439,7 +440,7 @@
it.println("StatusBarWindowBlurController:")
it.increaseIndent()
it.println("shadeExpansion: $shadeExpansion")
- it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}")
+ it.println("shouldApplyShadeBlur: ${shouldApplyShadeBlur()}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 2c1296f..7fbb0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -22,15 +22,18 @@
private val headsUpManager: HeadsUpManagerPhone,
private val jankMonitor: InteractionJankMonitor
) {
+ @JvmOverloads
fun getAnimatorController(
- notification: ExpandableNotificationRow
+ notification: ExpandableNotificationRow,
+ onFinishAnimationCallback: Runnable = Runnable {}
): NotificationLaunchAnimatorController {
return NotificationLaunchAnimatorController(
notificationShadeWindowViewController,
notificationListContainer,
headsUpManager,
notification,
- jankMonitor
+ jankMonitor,
+ onFinishAnimationCallback
)
}
}
@@ -45,7 +48,8 @@
private val notificationListContainer: NotificationListContainer,
private val headsUpManager: HeadsUpManagerPhone,
private val notification: ExpandableNotificationRow,
- private val jankMonitor: InteractionJankMonitor
+ private val jankMonitor: InteractionJankMonitor,
+ private val onFinishAnimationCallback: Runnable
) : ActivityLaunchAnimator.Controller {
companion object {
@@ -119,6 +123,7 @@
if (!willAnimate) {
removeHun(animate = true)
+ onFinishAnimationCallback.run()
}
}
@@ -137,6 +142,7 @@
notificationShadeWindowViewController.setExpandAnimationRunning(false)
notificationEntry.isExpandAnimationRunning = false
removeHun(animate = true)
+ onFinishAnimationCallback.run()
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -156,6 +162,7 @@
notificationListContainer.setExpandingNotification(null)
applyParams(null)
removeHun(animate = false)
+ onFinishAnimationCallback.run()
}
private fun applyParams(params: ExpandAnimationParameters?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 369ef34..2baa079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -230,7 +230,13 @@
mPipelineState.requireState(STATE_IDLE);
mNotifSections.clear();
+ NotifSectioner lastSection = null;
for (NotifSectioner sectioner : sectioners) {
+ if (lastSection != null && lastSection.getBucket() > sectioner.getBucket()) {
+ throw new IllegalArgumentException("setSectioners with non contiguous sections "
+ + lastSection.getName() + " - " + lastSection.getBucket() + " & "
+ + sectioner.getName() + " - " + sectioner.getBucket());
+ }
final NotifSection section = new NotifSection(sectioner, mNotifSections.size());
final NotifComparator sectionComparator = section.getComparator();
mNotifSections.add(section);
@@ -238,6 +244,7 @@
if (sectionComparator != null) {
sectionComparator.setInvalidationListener(this::onNotifComparatorInvalidated);
}
+ lastSection = sectioner;
}
mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size()));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 0df2162..da0169b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -72,10 +72,11 @@
private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
private lateinit var mNotifPipeline: NotifPipeline
private var mNow: Long = -1
- // notifs we've extended the lifetime for
- private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>()
private val mPostedEntries = LinkedHashMap<String, PostedEntry>()
+ // notifs we've extended the lifetime for with cancellation callbacks
+ private val mNotifsExtendingLifetime = ArrayMap<NotificationEntry, Runnable?>()
+
override fun attach(pipeline: NotifPipeline) {
mNotifPipeline = pipeline
mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
@@ -460,23 +461,20 @@
}
if (isSticky(entry)) {
val removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
- mExecutor.executeDelayed({
- val canStillRemove = mHeadsUpManager.canRemoveImmediately(entry.key)
- if (mNotifsExtendingLifetime.contains(entry) && canStillRemove) {
- mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true)
- }
+ mNotifsExtendingLifetime[entry] = mExecutor.executeDelayed({
+ mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true)
}, removeAfterMillis)
} else {
mExecutor.execute {
mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ false)
}
+ mNotifsExtendingLifetime[entry] = null
}
- mNotifsExtendingLifetime.add(entry)
return true
}
override fun cancelLifetimeExtension(entry: NotificationEntry) {
- mNotifsExtendingLifetime.remove(entry)
+ mNotifsExtendingLifetime.remove(entry)?.run()
}
}
@@ -543,7 +541,8 @@
mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry)
private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
- if (mNotifsExtendingLifetime.remove(entry)) {
+ if (mNotifsExtendingLifetime.contains(entry)) {
+ mNotifsExtendingLifetime.remove(entry)?.run()
mEndLifetimeExtension?.onEndLifetimeExtension(mLifetimeExtender, entry)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 22300d8..1237c70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -16,36 +16,16 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import static android.app.Notification.VISIBILITY_SECRET;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
-
-import androidx.annotation.MainThread;
-
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import javax.inject.Inject;
@@ -56,171 +36,48 @@
@CoordinatorScope
public class KeyguardCoordinator implements Coordinator {
private static final String TAG = "KeyguardCoordinator";
-
- private final Context mContext;
- private final Handler mMainHandler;
- private final KeyguardStateController mKeyguardStateController;
- private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final BroadcastDispatcher mBroadcastDispatcher;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final HighPriorityProvider mHighPriorityProvider;
private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
-
- private boolean mHideSilentNotificationsOnLockscreen;
+ private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
@Inject
public KeyguardCoordinator(
- Context context,
- @MainThread Handler mainThreadHandler,
- KeyguardStateController keyguardStateController,
- NotificationLockscreenUserManager lockscreenUserManager,
- BroadcastDispatcher broadcastDispatcher,
StatusBarStateController statusBarStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
HighPriorityProvider highPriorityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider) {
- mContext = context;
- mMainHandler = mainThreadHandler;
- mKeyguardStateController = keyguardStateController;
- mLockscreenUserManager = lockscreenUserManager;
- mBroadcastDispatcher = broadcastDispatcher;
+ SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mHighPriorityProvider = highPriorityProvider;
mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
+ mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
}
@Override
public void attach(NotifPipeline pipeline) {
- readShowSilentNotificationSetting();
setupInvalidateNotifListCallbacks();
// Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
pipeline.addFinalizeFilter(mNotifFilter);
-
+ mKeyguardNotificationVisibilityProvider
+ .addOnStateChangedListener(this::invalidateListFromFilter);
updateSectionHeadersVisibility();
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- final StatusBarNotification sbn = entry.getSbn();
-
- // FILTER OUT the notification when the keyguard is showing and...
- if (mKeyguardStateController.isShowing()) {
- // ... user settings or the device policy manager doesn't allow lockscreen
- // notifications;
- if (!mLockscreenUserManager.shouldShowLockscreenNotifications()) {
- return true;
- }
-
- final int currUserId = mLockscreenUserManager.getCurrentUserId();
- final int notifUserId = (sbn.getUser().getIdentifier() == UserHandle.USER_ALL)
- ? currUserId : sbn.getUser().getIdentifier();
-
- // ... user is in lockdown
- if (mKeyguardUpdateMonitor.isUserInLockdown(currUserId)
- || mKeyguardUpdateMonitor.isUserInLockdown(notifUserId)) {
- return true;
- }
-
- // ... device is in public mode and the user's settings doesn't allow
- // notifications to show in public mode
- if (mLockscreenUserManager.isLockscreenPublicMode(currUserId)
- || mLockscreenUserManager.isLockscreenPublicMode(notifUserId)) {
- if (entry.getRanking().getLockscreenVisibilityOverride() == VISIBILITY_SECRET) {
- return true;
- }
-
- if (!mLockscreenUserManager.userAllowsNotificationsInPublic(currUserId)
- || !mLockscreenUserManager.userAllowsNotificationsInPublic(
- notifUserId)) {
- return true;
- }
- }
-
- // ... neither this notification nor its group have high enough priority
- // to be shown on the lockscreen
- if (entry.getParent() != null) {
- final GroupEntry parent = entry.getParent();
- if (priorityExceedsLockscreenShowingThreshold(parent)) {
- return false;
- }
- }
- return !priorityExceedsLockscreenShowingThreshold(entry);
- }
- return false;
+ return mKeyguardNotificationVisibilityProvider.hideNotification(entry);
}
};
- private boolean priorityExceedsLockscreenShowingThreshold(ListEntry entry) {
- if (entry == null) {
- return false;
- }
- if (mHideSilentNotificationsOnLockscreen) {
- return mHighPriorityProvider.isHighPriority(entry);
- } else {
- return entry.getRepresentativeEntry() != null
- && !entry.getRepresentativeEntry().getRanking().isAmbient();
- }
- }
-
// TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
// these same updates
private void setupInvalidateNotifListCallbacks() {
- // register onKeyguardShowing callback
- mKeyguardStateController.addCallback(mKeyguardCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
- // register lockscreen settings changed callbacks:
- final ContentObserver settingsObserver = new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (uri.equals(Settings.Secure.getUriFor(
- Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS))) {
- readShowSilentNotificationSetting();
- }
-
- if (mKeyguardStateController.isShowing()) {
- invalidateListFromFilter("Settings " + uri + " changed");
- }
- }
- };
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS),
- false,
- settingsObserver,
- UserHandle.USER_ALL);
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
- true,
- settingsObserver,
- UserHandle.USER_ALL);
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
- false,
- settingsObserver);
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS),
- false,
- settingsObserver,
- UserHandle.USER_ALL);
-
- // register (maybe) public mode changed callbacks:
- mStatusBarStateController.addCallback(mStatusBarStateListener);
- mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mKeyguardStateController.isShowing()) {
- // maybe public mode changed
- invalidateListFromFilter(intent.getAction());
- }
- }}, new IntentFilter(Intent.ACTION_USER_SWITCHED));
}
private void invalidateListFromFilter(String reason) {
@@ -228,49 +85,10 @@
mNotifFilter.invalidateList();
}
- private void readShowSilentNotificationSetting() {
- mHideSilentNotificationsOnLockscreen =
- Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
- 1) == 0;
- }
-
private void updateSectionHeadersVisibility() {
boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
boolean showSections = !onKeyguard && !neverShowSections;
mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
}
-
- private final KeyguardStateController.Callback mKeyguardCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onUnlockedChanged() {
- invalidateListFromFilter("onUnlockedChanged");
- }
-
- @Override
- public void onKeyguardShowingChanged() {
- invalidateListFromFilter("onKeyguardShowingChanged");
- }
- };
-
- private final StatusBarStateController.StateListener mStatusBarStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onStateChanged(int newState) {
- // maybe public mode changed
- invalidateListFromFilter("onStatusBarStateChanged");
- }
- };
-
- private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onStrongAuthStateChanged(int userId) {
- // maybe lockdown mode changed
- invalidateListFromFilter("onStrongAuthStateChanged");
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index cd2affe..7c4e449 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -73,7 +73,7 @@
GroupExpansionManager,
Dumpable {
- private static final String TAG = "NotifGroupManager";
+ private static final String TAG = "LegacyNotifGroupManager";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index c35b77c..6db544c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -57,6 +57,7 @@
var currentSection: NotifSection? = null
val prevSections = mutableSetOf<NotifSection?>()
+ var lastSection: NotifSection? = null
val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible
val sectionOrder = mutableListOf<NotifSection?>()
val sectionHeaders = mutableMapOf<NotifSection?, NodeController?>()
@@ -65,6 +66,14 @@
for (entry in notifList) {
val section = entry.section!!
+ lastSection?.let {
+ if (it.bucket > section.bucket) {
+ throw IllegalStateException("buildNodeSpec with non contiguous section " +
+ "buckets ${it.sectioner.name} - ${it.bucket} & " +
+ "${it.sectioner.name} - ${it.bucket}")
+ }
+ }
+ lastSection = section
if (prevSections.contains(section)) {
throw java.lang.RuntimeException("Section ${section.label} has been duplicated")
}
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
new file mode 100644
index 0000000..70c9a16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -0,0 +1,186 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import android.app.Notification
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.ListenerSet
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * Determines if notifications should be visible based on the state of the keyguard
+ */
+class KeyguardNotificationVisibilityProvider @Inject constructor(
+ context: Context,
+ @Main private val handler: Handler,
+ private val keyguardStateController: KeyguardStateController,
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val highPriorityProvider: HighPriorityProvider,
+ private val statusBarStateController: StatusBarStateController,
+ private val broadcastDispatcher: BroadcastDispatcher
+) : CoreStartable(context) {
+ private val onStateChangedListeners = ListenerSet<Consumer<String>>()
+ private var hideSilentNotificationsOnLockscreen: Boolean = false
+
+ override fun start() {
+ readShowSilentNotificationSetting()
+ keyguardStateController.addCallback(object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ notifyStateChanged("onUnlockedChanged")
+ }
+
+ override fun onKeyguardShowingChanged() {
+ notifyStateChanged("onKeyguardShowingChanged")
+ }
+ })
+ keyguardUpdateMonitor.registerCallback(object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ notifyStateChanged("onStrongAuthStateChanged")
+ }
+ })
+
+ // register lockscreen settings changed callbacks:
+ val settingsObserver: ContentObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri) {
+ if (keyguardStateController.isShowing) {
+ notifyStateChanged("Settings $uri changed")
+ }
+ }
+ }
+
+ mContext.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS),
+ false,
+ settingsObserver,
+ UserHandle.USER_ALL)
+
+ mContext.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+ true,
+ settingsObserver,
+ UserHandle.USER_ALL)
+
+ mContext.contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ false,
+ settingsObserver)
+
+ mContext.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS),
+ false,
+ settingsObserver,
+ UserHandle.USER_ALL)
+
+ // register (maybe) public mode changed callbacks:
+ statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+ override fun onStateChanged(state: Int) {
+ notifyStateChanged("onStatusBarStateChanged")
+ }
+ })
+ broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (keyguardStateController.isShowing()) {
+ // maybe public mode changed
+ notifyStateChanged(intent.action)
+ }
+ }
+ }, IntentFilter(Intent.ACTION_USER_SWITCHED))
+ }
+
+ fun addOnStateChangedListener(listener: Consumer<String>) {
+ onStateChangedListeners.addIfAbsent(listener)
+ }
+
+ fun removeOnStateChangedListener(listener: Consumer<String>) {
+ onStateChangedListeners.remove(listener)
+ }
+
+ private fun notifyStateChanged(reason: String) {
+ onStateChangedListeners.forEach({ it.accept(reason) })
+ }
+
+ /**
+ * Determines if the given notification should be hidden based on the current keyguard state.
+ * If Listener#onKeyguardStateChanged is invoked, the results of this method may no longer
+ * be valid, and so should be re-queried
+ */
+ fun hideNotification(entry: NotificationEntry): Boolean {
+ val sbn = entry.sbn
+ // FILTER OUT the notification when the keyguard is showing and...
+ if (keyguardStateController.isShowing()) {
+ // ... user settings or the device policy manager doesn't allow lockscreen
+ // notifications;
+ if (!lockscreenUserManager.shouldShowLockscreenNotifications()) {
+ return true
+ }
+ val currUserId: Int = lockscreenUserManager.getCurrentUserId()
+ val notifUserId =
+ if (sbn.user.identifier == UserHandle.USER_ALL) currUserId
+ else sbn.user.identifier
+
+ // ... user is in lockdown
+ if (keyguardUpdateMonitor.isUserInLockdown(currUserId) ||
+ keyguardUpdateMonitor.isUserInLockdown(notifUserId)) {
+ return true
+ }
+
+ // ... device is in public mode and the user's settings doesn't allow
+ // notifications to show in public mode
+ if (lockscreenUserManager.isLockscreenPublicMode(currUserId) ||
+ lockscreenUserManager.isLockscreenPublicMode(notifUserId)) {
+ if (entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET) {
+ return true
+ }
+ if (!lockscreenUserManager.userAllowsNotificationsInPublic(currUserId) ||
+ !lockscreenUserManager.userAllowsNotificationsInPublic(
+ notifUserId)) {
+ return true
+ }
+ }
+
+ // ... neither this notification nor its group have high enough priority
+ // to be shown on the lockscreen
+ if (entry.parent != null) {
+ val parent = entry.parent
+ if (priorityExceedsLockscreenShowingThreshold(parent)) {
+ return false
+ }
+ }
+ return !priorityExceedsLockscreenShowingThreshold(entry)
+ }
+ return false
+ }
+
+ private fun priorityExceedsLockscreenShowingThreshold(entry: ListEntry?): Boolean =
+ when {
+ entry == null -> false
+ hideSilentNotificationsOnLockscreen -> highPriorityProvider.isHighPriority(entry)
+ else -> entry.representativeEntry?.ranking?.isAmbient == false
+ }
+
+ private fun readShowSilentNotificationSetting() {
+ hideSilentNotificationsOnLockscreen = Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+ 1) == 0
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index c991376..6c99e3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -211,6 +211,14 @@
"Pulsing: $str1"
})
}
+
+ fun keyguardHideNotification(key: String) {
+ hunBuffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "Keyguard Hide Notification: $str1"
+ })
+ }
}
private const val TAG = "InterruptionStateProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 7ed2699..c1771cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -36,6 +36,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -66,6 +67,8 @@
private final ContentObserver mHeadsUpObserver;
private final HeadsUpManager mHeadsUpManager;
private final NotificationInterruptLogger mLogger;
+ private final NotifPipelineFlags mFlags;
+ private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
@@ -81,7 +84,9 @@
StatusBarStateController statusBarStateController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
- @Main Handler mainHandler) {
+ @Main Handler mainHandler,
+ NotifPipelineFlags flags,
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
mContentResolver = contentResolver;
mPowerManager = powerManager;
mDreamManager = dreamManager;
@@ -91,6 +96,8 @@
mStatusBarStateController = statusBarStateController;
mHeadsUpManager = headsUpManager;
mLogger = logger;
+ mFlags = flags;
+ mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
mHeadsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -282,7 +289,7 @@
private boolean canAlertCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.getSbn();
- if (mNotificationFilter.shouldFilterOut(entry)) {
+ if (!mFlags.isNewPipelineEnabled() && mNotificationFilter.shouldFilterOut(entry)) {
mLogger.logNoAlertingFilteredOut(sbn);
return false;
}
@@ -305,6 +312,11 @@
return false;
}
+ if (mKeyguardNotificationVisibilityProvider.hideNotification(entry)) {
+ mLogger.keyguardHideNotification(entry.getKey());
+ return false;
+ }
+
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7df8e7d..6bbecc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1233,10 +1233,6 @@
mView.forceNoOverlappingRendering(force);
}
- public void setTranslationX(float translation) {
- mView.setTranslationX(translation);
- }
-
public void setExpandingVelocity(float velocity) {
mView.setExpandingVelocity(velocity);
}
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 a7f950e..ec2d608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -2881,8 +2881,7 @@
}
boolean updateIsKeyguard(boolean forceStateChange) {
- boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+ boolean wakeAndUnlocking = mBiometricUnlockController.isWakeAndUnlock();
// For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise
// there's no surface we can show to the user. Note that the device goes fully interactive
@@ -2892,7 +2891,7 @@
&& (!mDeviceInteractive || (isGoingToSleep()
&& (isScreenFullyOff()
|| (mKeyguardStateController.isShowing() && !isOccluded()))));
- boolean isWakingAndOccluded = isOccluded() && isWaking();
+ boolean isWakingAndOccluded = isOccluded() && isWakingOrAwake();
boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
|| keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded;
if (keyguardForDozing) {
@@ -3076,7 +3075,6 @@
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
mNotificationPanelViewController.onAffordanceLaunchEnded();
- mNotificationPanelViewController.cancelAnimation();
mNotificationPanelViewController.resetAlpha();
mNotificationPanelViewController.resetTranslation();
mNotificationPanelViewController.resetViewGroupFade();
@@ -3702,8 +3700,9 @@
== WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
}
- boolean isWaking() {
- return mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_WAKING;
+ boolean isWakingOrAwake() {
+ return mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_WAKING
+ || mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
}
public void notifyBiometricAuthModeChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index dc1af36..7f1611f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -555,6 +555,7 @@
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
mControlsButton.setContentDescription(getContext()
.getString(mControlsComponent.getTileTitleId()));
+ updateAffordanceColors();
boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> c.getFavorites().size() > 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index cc8a703..cbdf5c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,13 +18,9 @@
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.View.GONE;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
-import static androidx.constraintlayout.widget.ConstraintSet.START;
-import static androidx.constraintlayout.widget.ConstraintSet.TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -620,6 +616,11 @@
*/
private float mKeyguardOnlyContentAlpha = 1.0f;
+ /**
+ * The translationY of the views which only show on the keyguard but in shade / shade locked.
+ */
+ private int mKeyguardOnlyTransitionTranslationY = 0;
+
private float mUdfpsMaxYBurnInOffset;
/**
@@ -1080,14 +1081,10 @@
public void updateResources() {
mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
- mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
mSplitShadeNotificationsScrimMarginBottom =
mResources.getDimensionPixelSize(
R.dimen.split_shade_notifications_scrim_margin_bottom);
- int panelMarginHorizontal = mResources.getDimensionPixelSize(
- R.dimen.notification_panel_margin_horizontal);
-
final boolean newShouldUseSplitNotificationShade =
Utils.shouldUseSplitNotificationShade(mResources);
final boolean splitNotificationShadeChanged =
@@ -1097,49 +1094,12 @@
if (mQs != null) {
mQs.setInSplitShade(mShouldUseSplitNotificationShade);
}
-
- int notificationsBottomMargin = mResources.getDimensionPixelSize(
- R.dimen.notification_panel_margin_bottom);
+ mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade);
-
- // To change the constraints at runtime, all children of the ConstraintLayout must have ids
- ensureAllViewsHaveIds(mNotificationContainerParent);
- ConstraintSet constraintSet = new ConstraintSet();
- constraintSet.clone(mNotificationContainerParent);
- int statusViewMarginHorizontal = mResources.getDimensionPixelSize(
- R.dimen.status_view_margin_horizontal);
- constraintSet.setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal);
- constraintSet.setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal);
- if (mShouldUseSplitNotificationShade) {
- // width = 0 to take up all available space within constraints
- constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END);
- constraintSet.connect(
- R.id.notification_stack_scroller, START,
- R.id.qs_edge_guideline, START);
- constraintSet.constrainHeight(R.id.split_shade_status_bar, mSplitShadeStatusBarHeight);
- } else {
- constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
- constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
- if (mUseCombinedQSHeaders) {
- constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT);
- }
- }
- constraintSet.setMargin(R.id.notification_stack_scroller, START,
- mShouldUseSplitNotificationShade ? 0 : panelMarginHorizontal);
- constraintSet.setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal);
- constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
- constraintSet.setMargin(R.id.notification_stack_scroller, BOTTOM,
- notificationsBottomMargin);
- constraintSet.setMargin(R.id.qs_frame, START, panelMarginHorizontal);
- constraintSet.setMargin(R.id.qs_frame, END,
- mShouldUseSplitNotificationShade ? 0 : panelMarginHorizontal);
- constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
- constraintSet.applyTo(mNotificationContainerParent);
mAmbientState.setStackTopMargin(topMargin);
- mNotificationsQSContainerController.updateMargins();
- mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
+ mNotificationsQSContainerController.updateResources();
updateKeyguardStatusViewAlignment(/* animate= */false);
@@ -1150,15 +1110,6 @@
}
}
- private static void ensureAllViewsHaveIds(ViewGroup parentView) {
- for (int i = 0; i < parentView.getChildCount(); i++) {
- View childView = parentView.getChildAt(i);
- if (childView.getId() == View.NO_ID) {
- childView.setId(View.generateViewId());
- }
- }
- }
-
private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
View view = mView.findViewById(viewId);
if (view != null) {
@@ -1629,6 +1580,8 @@
private void updateClock() {
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
mKeyguardStatusViewController.setAlpha(alpha);
+ mKeyguardStatusViewController
+ .setTranslationYExcludingMedia(mKeyguardOnlyTransitionTranslationY);
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setAlpha(alpha);
}
@@ -2786,11 +2739,12 @@
}
/**
- * Set the alpha of the keyguard elements which only show on the lockscreen, but not in
- * shade locked / shade. This is used when dragging down to the full shade.
+ * Set the alpha and translationY of the keyguard elements which only show on the lockscreen,
+ * but not in shade locked / shade. This is used when dragging down to the full shade.
*/
- public void setKeyguardOnlyContentAlpha(float keyguardAlpha) {
+ public void setKeyguardTransitionProgress(float keyguardAlpha, int keyguardTranslationY) {
mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha);
+ mKeyguardOnlyTransitionTranslationY = keyguardTranslationY;
if (mBarState == KEYGUARD) {
// If the animator is running, it's already fading out the content and this is a reset
mBottomAreaShadeAlpha = mKeyguardOnlyContentAlpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 0ff010a..16e5732 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -386,10 +386,12 @@
}
visible = true;
}
- if (visible) {
- mNotificationShadeView.setVisibility(View.VISIBLE);
- } else {
- mNotificationShadeView.setVisibility(View.INVISIBLE);
+ if (mNotificationShadeView != null) {
+ if (visible) {
+ mNotificationShadeView.setVisibility(View.VISIBLE);
+ } else {
+ mNotificationShadeView.setVisibility(View.INVISIBLE);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index ebb09b1..7764d338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -1,6 +1,15 @@
package com.android.systemui.statusbar.phone
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowInsets
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -10,6 +19,7 @@
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.util.Utils
import com.android.systemui.util.ViewController
import java.util.function.Consumer
import javax.inject.Inject
@@ -28,23 +38,20 @@
mView.invalidate()
}
}
- var splitShadeEnabled = false
- set(value) {
- if (field != value) {
- field = value
- // in case device configuration changed while showing QS details/customizer
- updateBottomSpacing()
- }
- }
-
+ private var splitShadeEnabled = false
private var isQSDetailShowing = false
private var isQSCustomizing = false
private var isQSCustomizerAnimating = false
+ private var splitShadeStatusBarHeight = 0
private var notificationsBottomMargin = 0
private var scrimShadeBottomMargin = 0
private var bottomStableInsets = 0
private var bottomCutoutInsets = 0
+ private var panelMarginHorizontal = 0
+ private var topMargin = 0
+
+ private val useCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
private var isGestureNavigation = true
private var taskbarVisible = false
@@ -68,7 +75,6 @@
}
public override fun onViewAttached() {
- updateMargins()
updateResources()
overviewProxyService.addCallback(taskbarVisibilityListener)
mView.setInsetsChangedListener(windowInsetsListener)
@@ -83,7 +89,27 @@
mView.setConfigurationChangedListener(null)
}
- private fun updateResources() {
+ fun updateResources() {
+ val newSplitShadeEnabled = Utils.shouldUseSplitNotificationShade(resources)
+ val splitShadeEnabledChanged = newSplitShadeEnabled != splitShadeEnabled
+ splitShadeEnabled = newSplitShadeEnabled
+ notificationsBottomMargin = resources.getDimensionPixelSize(
+ R.dimen.notification_panel_margin_bottom)
+ splitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(context)
+ panelMarginHorizontal = resources.getDimensionPixelSize(
+ R.dimen.notification_panel_margin_horizontal)
+ topMargin = if (splitShadeEnabled) {
+ splitShadeStatusBarHeight
+ } else {
+ resources.getDimensionPixelSize(R.dimen.notification_panel_margin_top)
+ }
+ updateConstraints()
+ if (splitShadeEnabledChanged) {
+ // Let's do it at the end when all margins/paddings were already applied.
+ // We need to updateBottomSpacing() in case device configuration changed while showing
+ // QS details/customizer
+ updateBottomSpacing()
+ }
val previousScrimShadeBottomMargin = scrimShadeBottomMargin
scrimShadeBottomMargin = resources.getDimensionPixelSize(
R.dimen.split_shade_notifications_scrim_margin_bottom
@@ -94,15 +120,6 @@
}
}
- /**
- * Update the notification bottom margin.
- *
- * Will not call updateBottomSpacing
- */
- fun updateMargins() {
- notificationsBottomMargin = mView.defaultNotificationsMarginBottom
- }
-
override fun setCustomizerAnimating(animating: Boolean) {
if (isQSCustomizerAnimating != animating) {
isQSCustomizerAnimating = animating
@@ -178,4 +195,66 @@
}
return containerPadding to stackScrollMargin
}
-}
\ No newline at end of file
+
+ fun updateConstraints() {
+ // To change the constraints at runtime, all children of the ConstraintLayout must have ids
+ ensureAllViewsHaveIds(mView)
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(mView)
+ setKeyguardStatusViewConstraints(constraintSet)
+ setQsConstraints(constraintSet)
+ setNotificationsConstraints(constraintSet)
+ setSplitShadeStatusBarConstraints(constraintSet)
+ mView.applyConstraints(constraintSet)
+ }
+
+ private fun setSplitShadeStatusBarConstraints(constraintSet: ConstraintSet) {
+ if (splitShadeEnabled) {
+ constraintSet.constrainHeight(R.id.split_shade_status_bar, splitShadeStatusBarHeight)
+ } else {
+ if (useCombinedQSHeaders) {
+ constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT)
+ }
+ }
+ }
+
+ private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
+ val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+ constraintSet.apply {
+ connect(R.id.notification_stack_scroller, START, startConstraintId, START)
+ setMargin(R.id.notification_stack_scroller, START,
+ if (splitShadeEnabled) 0 else panelMarginHorizontal)
+ setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
+ setMargin(R.id.notification_stack_scroller, TOP, topMargin)
+ setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
+ }
+ }
+
+ private fun setQsConstraints(constraintSet: ConstraintSet) {
+ val endConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+ constraintSet.apply {
+ connect(R.id.qs_frame, END, endConstraintId, END)
+ setMargin(R.id.qs_frame, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+ setMargin(R.id.qs_frame, END, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+ setMargin(R.id.qs_frame, TOP, topMargin)
+ }
+ }
+
+ private fun setKeyguardStatusViewConstraints(constraintSet: ConstraintSet) {
+ val statusViewMarginHorizontal = resources.getDimensionPixelSize(
+ R.dimen.status_view_margin_horizontal)
+ constraintSet.apply {
+ setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal)
+ setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal)
+ }
+ }
+
+ private fun ensureAllViewsHaveIds(parentView: ViewGroup) {
+ for (i in 0 until parentView.childCount) {
+ val childView = parentView.getChildAt(i)
+ if (childView.id == View.NO_ID) {
+ childView.id = View.generateViewId()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index c2b5f56..7caea06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -26,6 +26,7 @@
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import com.android.systemui.R;
import com.android.systemui.fragments.FragmentHostManager;
@@ -123,10 +124,6 @@
}
}
- public int getDefaultNotificationsMarginBottom() {
- return ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
- }
-
public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
mInsetsChangedListener = onInsetsChangedListener;
}
@@ -197,4 +194,7 @@
}
}
+ public void applyConstraints(ConstraintSet constraintSet) {
+ constraintSet.applyTo(this);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 1d560c4..942aab5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -382,7 +382,7 @@
protected void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
- if (!mHandlingPointerUp) {
+ if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
mInitialOffsetOnTouch = expandedHeight;
@@ -654,7 +654,9 @@
@Override
public void onAnimationStart(Animator animation) {
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ if (!mStatusBarStateController.isDozing()) {
+ beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 108d98a..637e4be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -370,6 +370,8 @@
mLogger.logExpandingBubble(notificationKey);
removeHunAfterClick(row);
expandBubbleStackOnMainThread(entry);
+ mMainThreadHandler.post(
+ () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry));
} else {
startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent);
}
@@ -395,7 +397,6 @@
mMainThreadHandler.post(() -> {
final Runnable removeNotification = () -> {
mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK, summaryToRemove);
- mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry);
};
if (mPresenter.isCollapsing()) {
// To avoid lags we're only performing the remove
@@ -405,9 +406,6 @@
removeNotification.run();
}
});
- } else {
- mMainThreadHandler.post(
- () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry));
}
mIsCollapsingToShowActivityOverLockscreen = false;
@@ -483,14 +481,19 @@
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
+ Runnable onFinishAnimationCallback =
+ () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry);
ActivityLaunchAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
- mNotificationAnimationProvider.getAnimatorController(row),
+ mNotificationAnimationProvider
+ .getAnimatorController(row, onFinishAnimationCallback),
mCentralSurfaces,
isActivityIntent);
-
- mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
- animate, intent.getCreatorPackage(), (adapter) -> {
+ mActivityLaunchAnimator.startPendingIntentWithAnimation(
+ animationController,
+ animate,
+ intent.getCreatorPackage(),
+ (adapter) -> {
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
? getActivityOptions(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index ad47e2b..01fe865 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -219,6 +220,7 @@
new CameraManager.TorchCallback() {
@Override
+ @WorkerThread
public void onTorchModeUnavailable(String cameraId) {
if (TextUtils.equals(cameraId, mCameraId)) {
setCameraAvailable(false);
@@ -229,6 +231,7 @@
}
@Override
+ @WorkerThread
public void onTorchModeChanged(String cameraId, boolean enabled) {
if (TextUtils.equals(cameraId, mCameraId)) {
setCameraAvailable(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index fa26a35..763f041 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -67,6 +67,7 @@
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -122,6 +123,7 @@
protected final Handler mHandler;
private final ActivityStarter mActivityStarter;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final BroadcastSender mBroadcastSender;
private final TelephonyListenerManager mTelephonyListenerManager;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
@@ -165,6 +167,7 @@
@Main Handler handler,
ActivityStarter activityStarter,
BroadcastDispatcher broadcastDispatcher,
+ BroadcastSender broadcastSender,
UiEventLogger uiEventLogger,
FalsingManager falsingManager,
TelephonyListenerManager telephonyListenerManager,
@@ -179,6 +182,7 @@
mActivityManager = activityManager;
mUserTracker = userTracker;
mBroadcastDispatcher = broadcastDispatcher;
+ mBroadcastSender = broadcastSender;
mTelephonyListenerManager = telephonyListenerManager;
mUiEventLogger = uiEventLogger;
mFalsingManager = falsingManager;
@@ -1194,7 +1198,7 @@
}
// Use broadcast instead of ShadeController, as this dialog may have started in
// another process and normal dagger bindings are not available
- getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ mBroadcastSender.closeSystemDialogs();
getContext().startActivityAsUser(
CreateUserActivity.createIntentForStart(getContext()),
mUserTracker.getUserHandle());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index e980eb7..8e1e42a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -88,6 +88,6 @@
fun onPause_clearsTextField() {
mKeyguardPatternViewController.init()
mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+ verify(mKeyguardMessageAreaController).setMessage("")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 2fc9122..1753157 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -112,4 +112,13 @@
mKeyguardUpdateMonitorCallbackCaptor.getValue().onUserSwitchComplete(0);
verify(mKeyguardClockSwitchController).refreshFormat();
}
+
+ @Test
+ public void setTranslationYExcludingMedia_forwardsCallToView() {
+ float translationY = 123f;
+
+ mController.setTranslationYExcludingMedia(translationY);
+
+ verify(mKeyguardStatusView).setChildrenTranslationYExcludingMediaView(translationY);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
new file mode 100644
index 0000000..ce44f4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -0,0 +1,55 @@
+package com.android.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.children
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusViewTest : SysuiTestCase() {
+
+ private lateinit var keyguardStatusView: KeyguardStatusView
+ private val mediaView: View
+ get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+ private val statusViewContainer: ViewGroup
+ get() = keyguardStatusView.findViewById(R.id.status_view_container)
+ private val childrenExcludingMedia
+ get() = statusViewContainer.children.filter { it != mediaView }
+
+ @Before
+ fun setUp() {
+ keyguardStatusView = LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_status_view, /* root= */ null) as KeyguardStatusView
+ }
+
+ @Test
+ fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
+ val translationY = 1234f
+
+ keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+
+ assertThat(mediaView.translationY).isEqualTo(0)
+ }
+
+ @Test
+ fun setChildrenTranslationYExcludingMediaView_childrenAreTranslated() {
+ val translationY = 1234f
+
+ keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+
+ childrenExcludingMedia.forEach {
+ assertThat(it.translationY).isEqualTo(translationY)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt
new file mode 100644
index 0000000..214fd4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt
@@ -0,0 +1,277 @@
+package com.android.systemui.animation
+
+import android.animation.ObjectAnimator
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ViewBoundAnimatorTest : SysuiTestCase() {
+ companion object {
+ private const val TEST_DURATION = 1000L
+ private val TEST_INTERPOLATOR = Interpolators.LINEAR
+ }
+
+ private lateinit var rootView: LinearLayout
+
+ @Before
+ fun setUp() {
+ rootView = LinearLayout(mContext)
+ }
+
+ @After
+ fun tearDown() {
+ ViewBoundAnimator.stopAnimating(rootView)
+ }
+
+ @Test
+ fun respectsAnimationParameters() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(
+ rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
+ )
+ rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ val animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator
+ assertEquals(animator.interpolator, TEST_INTERPOLATOR)
+ assertEquals(animator.duration, TEST_DURATION)
+ }
+
+ @Test
+ fun animatesFromStartToEnd() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(rootView)
+ // Change all bounds.
+ rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ // The initial values should be those of the previous layout.
+ checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ // The end values should be those of the latest layout.
+ checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
+ }
+
+ @Test
+ fun animatesSuccessiveLayoutChanges() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(rootView)
+ // Change all bounds.
+ rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
+
+ // Change only top and right.
+ rootView.layout(0 /* l */, 20 /* t */, 60 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 20, r = 60, b = 80)
+
+ // Change all bounds again.
+ rootView.layout(5 /* l */, 25 /* t */, 55 /* r */, 95 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 5, t = 25, r = 55, b = 95)
+ }
+
+ @Test
+ fun animatesFromPreviousAnimationProgress() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR)
+ // Change all bounds.
+ rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ advanceAnimation(rootView, fraction = 0.5f)
+ checkBounds(rootView, l = 5, t = 15, r = 60, b = 65)
+
+ // Change all bounds again.
+ rootView.layout(25 /* l */, 25 /* t */, 55 /* r */, 60 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 5, t = 15, r = 60, b = 65)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 25, t = 25, r = 55, b = 60)
+ }
+
+ @Test
+ fun animatesRootAndChildren() {
+ val firstChild = View(mContext)
+ rootView.addView(firstChild)
+ val secondChild = View(mContext)
+ rootView.addView(secondChild)
+ rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
+ firstChild.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
+ secondChild.layout(100 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
+
+ ViewBoundAnimator.animate(rootView)
+ // Change all bounds.
+ rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+ firstChild.layout(10 /* l */, 20 /* t */, 150 /* r */, 120 /* b */)
+ secondChild.layout(150 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ // The initial values should be those of the previous layout.
+ checkBounds(rootView, l = 0, t = 0, r = 150, b = 100)
+ checkBounds(firstChild, l = 0, t = 0, r = 100, b = 100)
+ checkBounds(secondChild, l = 100, t = 0, r = 150, b = 100)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ // The end values should be those of the latest layout.
+ checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
+ checkBounds(firstChild, l = 10, t = 20, r = 150, b = 120)
+ checkBounds(secondChild, l = 150, t = 20, r = 200, b = 120)
+ }
+
+ @Test
+ fun doesNotAnimateInvisibleViews() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(rootView)
+ // GONE.
+ rootView.visibility = View.GONE
+ rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */)
+
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 55, b = 80)
+
+ // INVISIBLE.
+ rootView.visibility = View.INVISIBLE
+ rootView.layout(0 /* l */, 20 /* t */, 0 /* r */, 20 /* b */)
+ }
+
+ @Test
+ fun doesNotAnimateUnchangingBounds() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(rootView)
+ // No bounds are changed.
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
+
+ // Change only right and bottom.
+ rootView.layout(10 /* l */, 10 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 10, t = 10, r = 70, b = 80)
+ }
+
+ @Test
+ fun doesNotAnimateExcludedBounds() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(
+ rootView,
+ bounds = setOf(ViewBoundAnimator.Bound.LEFT, ViewBoundAnimator.Bound.TOP),
+ interpolator = TEST_INTERPOLATOR
+ )
+ // Change all bounds.
+ rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ advanceAnimation(rootView, 0.5f)
+ checkBounds(rootView, l = 5, t = 15, r = 70, b = 80)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 20, r = 70, b = 80)
+ }
+
+ @Test
+ fun stopsAnimatingAfterSingleLayout() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animateNextUpdate(rootView)
+ // Change all bounds.
+ rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
+
+ // Change all bounds again.
+ rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
+
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
+ }
+
+ @Test
+ fun stopsAnimatingWhenInstructed() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+
+ ViewBoundAnimator.animate(rootView)
+ // Change all bounds.
+ rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
+
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
+
+ ViewBoundAnimator.stopAnimating(rootView)
+ // Change all bounds again.
+ rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
+
+ assertNull(rootView.getTag(R.id.tag_animator))
+ checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
+ }
+
+ private fun checkBounds(v: View, l: Int, t: Int, r: Int, b: Int) {
+ assertEquals(l, v.left)
+ assertEquals(t, v.top)
+ assertEquals(r, v.right)
+ assertEquals(b, v.bottom)
+ }
+
+ private fun advanceAnimation(rootView: View, fraction: Float) {
+ (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.setCurrentFraction(fraction)
+
+ if (rootView is ViewGroup) {
+ for (i in 0 until rootView.childCount) {
+ advanceAnimation(rootView.getChildAt(i), fraction)
+ }
+ }
+ }
+
+ private fun endAnimation(rootView: View) {
+ (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.end()
+
+ if (rootView is ViewGroup) {
+ for (i in 0 until rootView.childCount) {
+ endAnimation(rootView.getChildAt(i))
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 1856fda..613931f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -48,6 +48,7 @@
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -64,8 +65,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -188,6 +189,7 @@
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
private IUdfpsOverlayController mOverlayController;
@Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+ @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
@Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor;
@Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
private ScreenLifecycle.Observer mScreenObserver;
@@ -568,23 +570,24 @@
}
@Test
- public void playHapticOnTouchUdfpsArea() throws RemoteException {
+ public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
- // GIVEN that the overlay is showing
+ // GIVEN that the overlay is showing and a11y touch exploration enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
- // WHEN ACTION_DOWN is received
- verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
- MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
- downEvent.recycle();
- MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ // WHEN ACTION_HOVER is received
+ verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+ MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
+ mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
+ enterEvent.recycle();
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
+ mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
moveEvent.recycle();
// THEN tick haptic is played
@@ -600,4 +603,34 @@
assertEquals(VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
UdfpsController.VIBRATION_ATTRIBUTES.getUsage());
}
+
+ @Test
+ public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ // WHEN ACTION_DOWN is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ downEvent.recycle();
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ moveEvent.recycle();
+
+ // THEN NO haptic played
+ verify(mVibrator, never()).vibrate(
+ anyInt(),
+ anyString(),
+ any(),
+ anyString(),
+ any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
new file mode 100644
index 0000000..fbd2c91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.systemui.broadcast
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.wakelock.WakeLockFake
+import com.google.common.truth.Truth.assertThat
+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.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class BroadcastSenderTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var mockContext: Context
+
+ private lateinit var broadcastSender: BroadcastSender
+ private lateinit var executor: FakeExecutor
+ private lateinit var wakeLock: WakeLockFake
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+ wakeLock = WakeLockFake()
+ val wakeLockBuilder = WakeLockFake.Builder(mContext)
+ wakeLockBuilder.setWakeLock(wakeLock)
+ broadcastSender = BroadcastSender(mockContext, wakeLockBuilder, executor)
+ }
+
+ @Test
+ fun sendBroadcast_dispatchesWithWakelock() {
+ val intent = Intent(Intent.ACTION_VIEW)
+ broadcastSender.sendBroadcast(intent)
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcast(intent)
+ }
+ }
+
+ @Test
+ fun sendBroadcastWithPermission_dispatchesWithWakelock() {
+ val intent = Intent(Intent.ACTION_VIEW)
+ val permission = "Permission"
+ broadcastSender.sendBroadcast(intent, permission)
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcast(intent, permission)
+ }
+ }
+
+ @Test
+ fun sendBroadcastAsUser_dispatchesWithWakelock() {
+ val intent = Intent(Intent.ACTION_VIEW)
+ broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL)
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL)
+ }
+ }
+
+ @Test
+ fun sendBroadcastAsUserWithPermission_dispatchesWithWakelock() {
+ val intent = Intent(Intent.ACTION_VIEW)
+ val permission = "Permission"
+ broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL, permission)
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL, permission)
+ }
+ }
+
+ @Test
+ fun sendBroadcastAsUserWithPermissionAndOptions_dispatchesWithWakelock() {
+ val intent = Intent(Intent.ACTION_VIEW)
+ val permission = "Permission"
+ val options = Bundle()
+ options.putString("key", "value")
+
+ broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL, permission, options)
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL, permission, options)
+ }
+ }
+
+ @Test
+ fun sendBroadcastAsUserWithPermissionAndAppOp_dispatchesWithWakelock() {
+ val intent = Intent(Intent.ACTION_VIEW)
+ val permission = "Permission"
+
+ broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL, permission, 12)
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL, permission, 12)
+ }
+ }
+
+ @Test
+ fun sendCloseSystemDialogs_dispatchesWithWakelock() {
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ broadcastSender.closeSystemDialogs()
+
+ runExecutorAssertingWakelock {
+ verify(mockContext).sendBroadcast(intentCaptor.capture())
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+ }
+ }
+
+ private fun runExecutorAssertingWakelock(verification: () -> Unit) {
+ assertThat(wakeLock.isHeld).isTrue()
+ executor.runAllReady()
+ verification.invoke()
+ assertThat(wakeLock.isHeld).isFalse()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index da25c62..49eaf823 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -23,6 +23,7 @@
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
@@ -62,6 +63,8 @@
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
+ private lateinit var broadcastSender: BroadcastSender
+ @Mock
private lateinit var taskViewFactory: Optional<TaskViewFactory>
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var cvh: ControlViewHolder
@@ -94,6 +97,7 @@
bgExecutor,
uiExecutor,
activityStarter,
+ broadcastSender,
keyguardStateController,
taskViewFactory,
metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
index 87b9172..0166fa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -21,6 +21,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
import com.android.wm.shell.TaskView
import org.junit.Before
import org.junit.Test
@@ -39,6 +40,8 @@
@Mock
private lateinit var taskView: TaskView
@Mock
+ private lateinit var broadcastSender: BroadcastSender
+ @Mock
private lateinit var controlViewHolder: ControlViewHolder
@Mock
private lateinit var pendingIntent: PendingIntent
@@ -63,6 +66,7 @@
private fun createDialog(pendingIntent: PendingIntent): DetailDialog {
return DetailDialog(
mContext,
+ broadcastSender,
taskView,
pendingIntent,
controlViewHolder
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index ff9e13a..dcbdea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams;
-import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -30,7 +29,6 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
@@ -44,7 +42,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
- private static final int DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT = 100;
private static final int MAX_BURN_IN_OFFSET = 20;
private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10;
private static final long MILLIS_UNTIL_FULL_JITTER = 240 * 1000;
@@ -76,9 +73,6 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mResources.getDimensionPixelSize(
- R.dimen.dream_overlay_notifications_drag_area_height)).thenReturn(
- DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
@@ -100,13 +94,6 @@
}
@Test
- public void testSetsDreamOverlayNotificationsDragAreaHeight() {
- assertEquals(
- mController.getDreamOverlayNotificationsDragAreaHeight(),
- DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
- }
-
- @Test
public void testBurnInProtectionStartsWhenContentViewAttached() {
mController.onViewAttached();
verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 7d7ccb4..3657192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -174,19 +174,19 @@
}
@Test
- public void testShouldShowComplicationsTrueByDefault() {
+ public void testShouldShowComplicationsFalseByDefault() {
mService.onBind(new Intent());
- assertThat(mService.shouldShowComplications()).isTrue();
+ assertThat(mService.shouldShowComplications()).isFalse();
}
@Test
public void testShouldShowComplicationsSetByIntentExtra() {
final Intent intent = new Intent();
- intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, false);
+ intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true);
mService.onBind(intent);
- assertThat(mService.shouldShowComplications()).isFalse();
+ assertThat(mService.shouldShowComplications()).isTrue();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 49da4bd..3ce9889 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -158,6 +158,7 @@
public void testComplicationFilteringWhenShouldShowComplications() {
final DreamOverlayStateController stateController =
new DreamOverlayStateController(mExecutor);
+ stateController.setShouldShowComplications(true);
final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
final Complication weatherComplication = Mockito.mock(Complication.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 708fc91..cb68d81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -40,6 +40,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -59,6 +60,7 @@
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
@@ -85,6 +87,7 @@
private lateinit var bgExecutor: FakeExecutor
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var holder: PlayerViewHolder
@Mock private lateinit var sessionHolder: PlayerSessionViewHolder
@@ -119,8 +122,6 @@
private lateinit var actionPlayPause: ImageButton
private lateinit var actionNext: ImageButton
private lateinit var actionPrev: ImageButton
- private lateinit var actionStart: ImageButton
- private lateinit var actionEnd: ImageButton
@Mock private lateinit var longPressText: TextView
@Mock private lateinit var handler: Handler
private lateinit var settings: View
@@ -144,8 +145,8 @@
whenever(mediaViewController.expandedLayout).thenReturn(expandedSet)
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
- player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
- seekBarViewModel, Lazy { mediaDataManager },
+ player = MediaControlPanel(context, bgExecutor, activityStarter, broadcastSender,
+ mediaViewController, seekBarViewModel, Lazy { mediaDataManager },
mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
@@ -168,6 +169,17 @@
cancelText = TextView(context)
dismiss = FrameLayout(context)
dismissText = TextView(context)
+
+ action0 = ImageButton(context).also { it.setId(R.id.action0) }
+ action1 = ImageButton(context).also { it.setId(R.id.action1) }
+ action2 = ImageButton(context).also { it.setId(R.id.action2) }
+ action3 = ImageButton(context).also { it.setId(R.id.action3) }
+ action4 = ImageButton(context).also { it.setId(R.id.action4) }
+
+ actionPlayPause = ImageButton(context).also { it.setId(R.id.actionPlayPause) }
+ actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) }
+ actionNext = ImageButton(context).also { it.setId(R.id.actionNext) }
+
initPlayerHolderMocks()
initSessionHolderMocks()
@@ -208,90 +220,64 @@
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
}
- /** Mock view holder for the notification player */
- private fun initPlayerHolderMocks() {
- whenever(holder.player).thenReturn(view)
- whenever(holder.appIcon).thenReturn(appIcon)
- whenever(holder.albumView).thenReturn(albumView)
- whenever(holder.titleText).thenReturn(titleText)
- whenever(holder.artistText).thenReturn(artistText)
+ /**
+ * Initialize elements common to both view holders
+ */
+ private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) {
+ whenever(viewHolder.player).thenReturn(view)
+ whenever(viewHolder.appIcon).thenReturn(appIcon)
+ whenever(viewHolder.albumView).thenReturn(albumView)
+ whenever(viewHolder.titleText).thenReturn(titleText)
+ whenever(viewHolder.artistText).thenReturn(artistText)
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
- whenever(holder.seamless).thenReturn(seamless)
- whenever(holder.seamlessButton).thenReturn(seamlessButton)
- whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
- whenever(holder.seamlessText).thenReturn(seamlessText)
- whenever(holder.seekBar).thenReturn(seekBar)
- whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
- whenever(holder.totalTimeView).thenReturn(totalTimeView)
+ whenever(viewHolder.seamless).thenReturn(seamless)
+ whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
+ whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon)
+ whenever(viewHolder.seamlessText).thenReturn(seamlessText)
+ whenever(viewHolder.seekBar).thenReturn(seekBar)
// Action buttons
- action0 = ImageButton(context)
- whenever(holder.action0).thenReturn(action0)
- whenever(holder.getAction(R.id.action0)).thenReturn(action0)
- action1 = ImageButton(context)
- whenever(holder.action1).thenReturn(action1)
- whenever(holder.getAction(R.id.action1)).thenReturn(action1)
- action2 = ImageButton(context)
- whenever(holder.action2).thenReturn(action2)
- whenever(holder.getAction(R.id.action2)).thenReturn(action2)
- action3 = ImageButton(context)
- whenever(holder.action3).thenReturn(action3)
- whenever(holder.getAction(R.id.action3)).thenReturn(action3)
- action4 = ImageButton(context)
- whenever(holder.action4).thenReturn(action4)
- whenever(holder.getAction(R.id.action4)).thenReturn(action4)
+ whenever(viewHolder.action0).thenReturn(action0)
+ whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0)
+ whenever(viewHolder.action1).thenReturn(action1)
+ whenever(viewHolder.getAction(R.id.action1)).thenReturn(action1)
+ whenever(viewHolder.action2).thenReturn(action2)
+ whenever(viewHolder.getAction(R.id.action2)).thenReturn(action2)
+ whenever(viewHolder.action3).thenReturn(action3)
+ whenever(viewHolder.getAction(R.id.action3)).thenReturn(action3)
+ whenever(viewHolder.action4).thenReturn(action4)
+ whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4)
// Long press menu
- whenever(holder.longPressText).thenReturn(longPressText)
+ whenever(viewHolder.longPressText).thenReturn(longPressText)
whenever(longPressText.handler).thenReturn(handler)
- whenever(holder.settings).thenReturn(settings)
- whenever(holder.settingsText).thenReturn(settingsText)
- whenever(holder.cancel).thenReturn(cancel)
- whenever(holder.cancelText).thenReturn(cancelText)
- whenever(holder.dismiss).thenReturn(dismiss)
- whenever(holder.dismissText).thenReturn(dismissText)
+ whenever(viewHolder.settings).thenReturn(settings)
+ whenever(viewHolder.settingsText).thenReturn(settingsText)
+ whenever(viewHolder.cancel).thenReturn(cancel)
+ whenever(viewHolder.cancelText).thenReturn(cancelText)
+ whenever(viewHolder.dismiss).thenReturn(dismiss)
+ whenever(viewHolder.dismissText).thenReturn(dismissText)
+ }
+
+ /** Mock view holder for the notification player */
+ private fun initPlayerHolderMocks() {
+ initMediaViewHolderMocks(holder)
+
+ whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
+ whenever(holder.totalTimeView).thenReturn(totalTimeView)
}
/** Mock view holder for session player */
private fun initSessionHolderMocks() {
- whenever(sessionHolder.player).thenReturn(view)
- whenever(sessionHolder.albumView).thenReturn(albumView)
- whenever(sessionHolder.appIcon).thenReturn(appIcon)
- whenever(sessionHolder.titleText).thenReturn(titleText)
- whenever(sessionHolder.artistText).thenReturn(artistText)
- val seamlessBackground = mock(RippleDrawable::class.java)
- whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
- whenever(sessionHolder.seamless).thenReturn(seamless)
- whenever(sessionHolder.seamlessButton).thenReturn(seamlessButton)
- whenever(sessionHolder.seamlessIcon).thenReturn(seamlessIcon)
- whenever(sessionHolder.seamlessText).thenReturn(seamlessText)
- whenever(sessionHolder.seekBar).thenReturn(seekBar)
+ initMediaViewHolderMocks(sessionHolder)
- // Action buttons
- actionPlayPause = ImageButton(context)
+ // Semantic action buttons
whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause)
whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
- actionNext = ImageButton(context)
whenever(sessionHolder.actionNext).thenReturn(actionNext)
whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
- actionPrev = ImageButton(context)
whenever(sessionHolder.actionPrev).thenReturn(actionPrev)
whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
- actionStart = ImageButton(context)
- whenever(sessionHolder.actionStart).thenReturn(actionStart)
- whenever(sessionHolder.getAction(R.id.actionStart)).thenReturn(actionStart)
- actionEnd = ImageButton(context)
- whenever(sessionHolder.actionEnd).thenReturn(actionEnd)
- whenever(sessionHolder.getAction(R.id.actionEnd)).thenReturn(actionEnd)
-
- // Long press menu
- whenever(sessionHolder.longPressText).thenReturn(longPressText)
- whenever(sessionHolder.settings).thenReturn(settings)
- whenever(sessionHolder.settingsText).thenReturn(settingsText)
- whenever(sessionHolder.cancel).thenReturn(cancel)
- whenever(sessionHolder.cancelText).thenReturn(cancelText)
- whenever(sessionHolder.dismiss).thenReturn(dismiss)
- whenever(sessionHolder.dismissText).thenReturn(dismissText)
}
@After
@@ -316,8 +302,8 @@
val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next"),
- startCustom = MediaAction(icon, null, "custom 1"),
- endCustom = MediaAction(icon, null, "custom 2")
+ custom0 = MediaAction(icon, null, "custom 0"),
+ custom1 = MediaAction(icon, null, "custom 1")
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -325,7 +311,7 @@
player.bindPlayer(state, PACKAGE)
verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
- assertThat(action0.contentDescription).isEqualTo("custom 1")
+ assertThat(action0.contentDescription).isEqualTo("custom 0")
assertThat(action0.isEnabled()).isFalse()
verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
@@ -340,7 +326,7 @@
assertThat(action3.contentDescription).isEqualTo("next")
verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
- assertThat(action4.contentDescription).isEqualTo("custom 2")
+ assertThat(action4.contentDescription).isEqualTo("custom 1")
assertThat(action4.isEnabled()).isFalse()
}
@@ -353,28 +339,96 @@
val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next"),
- startCustom = MediaAction(icon, null, "custom 1"),
- endCustom = MediaAction(icon, null, "custom 2")
+ custom0 = MediaAction(icon, null, "custom 0"),
+ custom1 = MediaAction(icon, null, "custom 1")
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
player.bindPlayer(state, PACKAGE)
- assertThat(actionStart.contentDescription).isEqualTo("custom 1")
- assertThat(actionStart.isEnabled()).isFalse()
-
assertThat(actionPrev.isEnabled()).isFalse()
assertThat(actionPrev.drawable).isNull()
+ verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE)
assertThat(actionPlayPause.isEnabled()).isTrue()
assertThat(actionPlayPause.contentDescription).isEqualTo("play")
+ verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE)
assertThat(actionNext.isEnabled()).isTrue()
assertThat(actionNext.contentDescription).isEqualTo("next")
+ verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE)
- assertThat(actionEnd.contentDescription).isEqualTo("custom 2")
- assertThat(actionEnd.isEnabled()).isFalse()
+ // Called twice since these IDs are used as generic buttons
+ assertThat(action0.contentDescription).isEqualTo("custom 0")
+ assertThat(action0.isEnabled()).isFalse()
+ verify(collapsedSet, times(2)).setVisibility(R.id.action0, ConstraintSet.GONE)
+
+ assertThat(action1.contentDescription).isEqualTo("custom 1")
+ assertThat(action1.isEnabled()).isFalse()
+ verify(collapsedSet, times(2)).setVisibility(R.id.action1, ConstraintSet.GONE)
+
+ // Verify generic buttons are hidden
+ verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.GONE)
+ verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.GONE)
+
+ verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE)
+ verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.GONE)
+
+ verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
+ verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
+ }
+
+ @Test
+ fun bindNotificationActionsNewLayout() {
+ whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+ whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
+
+ val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+ val actions = listOf(
+ MediaAction(icon, Runnable {}, "previous"),
+ MediaAction(icon, Runnable {}, "play"),
+ MediaAction(icon, null, "next"),
+ MediaAction(icon, null, "custom 0"),
+ MediaAction(icon, Runnable {}, "custom 1")
+ )
+ val state = mediaData.copy(actions = actions,
+ actionsToShowInCompact = listOf(1, 2),
+ semanticActions = null)
+
+ player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
+ player.bindPlayer(state, PACKAGE)
+
+ // Verify semantic actions are hidden
+ verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE)
+ verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE)
+
+ verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE)
+ verify(expandedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE)
+
+ verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE)
+ verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE)
+
+ // Generic actions all enabled
+ assertThat(action0.contentDescription).isEqualTo("previous")
+ assertThat(action0.isEnabled()).isTrue()
+ verify(collapsedSet).setVisibility(R.id.action0, ConstraintSet.GONE)
+
+ assertThat(action1.contentDescription).isEqualTo("play")
+ assertThat(action1.isEnabled()).isTrue()
+ verify(collapsedSet).setVisibility(R.id.action1, ConstraintSet.VISIBLE)
+
+ assertThat(action2.contentDescription).isEqualTo("next")
+ assertThat(action2.isEnabled()).isFalse()
+ verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
+
+ assertThat(action3.contentDescription).isEqualTo("custom 0")
+ assertThat(action3.isEnabled()).isFalse()
+ verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE)
+
+ assertThat(action4.contentDescription).isEqualTo("custom 1")
+ assertThat(action4.isEnabled()).isTrue()
+ verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 6b203bc..82a48ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -23,6 +23,7 @@
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -65,6 +66,8 @@
@Mock
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock
+ private lateinit var broadcastSender: BroadcastSender
+ @Mock
private lateinit var mediaResumeListener: MediaResumeListener
@Mock
private lateinit var mediaDataManager: MediaDataManager
@@ -87,7 +90,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
MediaPlayerData.clear()
- mediaDataFilter = MediaDataFilter(context, broadcastDispatcher,
+ mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, broadcastSender,
lockscreenUserManager, executor, clock)
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 021f70e..925ae30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -659,11 +659,11 @@
actions.nextOrCustom!!.action!!.run()
verify(transportControls).skipToNext()
- assertThat(actions.startCustom).isNotNull()
- assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+ assertThat(actions.custom0).isNotNull()
+ assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0])
- assertThat(actions.endCustom).isNotNull()
- assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+ assertThat(actions.custom1).isNotNull()
+ assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
@Test
@@ -697,11 +697,11 @@
assertThat(actions.nextOrCustom).isNotNull()
assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
- assertThat(actions.startCustom).isNotNull()
- assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2])
+ assertThat(actions.custom0).isNotNull()
+ assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[2])
- assertThat(actions.endCustom).isNotNull()
- assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3])
+ assertThat(actions.custom1).isNotNull()
+ assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
}
@Test
@@ -737,10 +737,10 @@
assertThat(actions.prevOrCustom).isNull()
assertThat(actions.nextOrCustom).isNull()
- assertThat(actions.startCustom).isNotNull()
- assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+ assertThat(actions.custom0).isNotNull()
+ assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0])
- assertThat(actions.endCustom).isNotNull()
- assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+ assertThat(actions.custom1).isNotNull()
+ assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index b359ae5..8e201b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -117,9 +117,9 @@
dreamOverlayStateController)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
- setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
- setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
- setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS)
+ setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
+ setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
+ setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
`when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
`when`(mediaCarouselController.mediaCarouselScrollHandler)
.thenReturn(mediaCarouselScrollHandler)
@@ -130,9 +130,9 @@
clearInvocations(mediaCarouselController)
}
- private fun setupHost(host: MediaHost, location: Int) {
+ private fun setupHost(host: MediaHost, location: Int, top: Int) {
`when`(host.location).thenReturn(location)
- `when`(host.currentBounds).thenReturn(Rect())
+ `when`(host.currentBounds).thenReturn(Rect(0, top, 0, top))
`when`(host.hostView).thenReturn(uniqueObjectHostView)
`when`(host.visible).thenReturn(true)
mediaHiearchyManager.register(host)
@@ -257,6 +257,20 @@
verify(mediaCarouselController).closeGuts()
}
+ @Test
+ fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() {
+ assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
+ }
+
+ @Test
+ fun getGuidedTransformationTranslationY_inGuidedTransformation_returnsCurrentTranslation() {
+ enterGuidedTransformation()
+
+ val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
+ assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY())
+ .isEqualTo(expectedTranslation)
+ }
+
private fun enableSplitShade() {
context.getOrCreateTestableResources().addOverride(
R.bool.config_use_split_notification_shade, true
@@ -284,4 +298,16 @@
private fun expandQS() {
mediaHiearchyManager.qsExpansion = 1.0f
}
+
+ private fun enterGuidedTransformation() {
+ mediaHiearchyManager.qsExpansion = 1.0f
+ goToLockscreen()
+ mediaHiearchyManager.setTransitionToFullShadeAmount(123f)
+ }
+
+ companion object {
+ private const val QQS_TOP = 123
+ private const val QS_TOP = 456
+ private const val LOCKSCREEN_TOP = 789
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 3c2392a..7ac15125 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -26,29 +26,33 @@
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
-public class SeekBarObserverTest : SysuiTestCase() {
+class SeekBarObserverTest : SysuiTestCase() {
private val disabledHeight = 1
private val enabledHeight = 2
private lateinit var observer: SeekBarObserver
@Mock private lateinit var mockHolder: PlayerViewHolder
+ @Mock private lateinit var mockSquigglyProgress: SquigglyProgress
private lateinit var seekBarView: SeekBar
private lateinit var elapsedTimeView: TextView
private lateinit var totalTimeView: TextView
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+
@Before
fun setUp() {
- mockHolder = mock(PlayerViewHolder::class.java)
context.orCreateTestableResources
.addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight)
@@ -56,6 +60,7 @@
.addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight)
seekBarView = SeekBar(context)
+ seekBarView.progressDrawable = mockSquigglyProgress
elapsedTimeView = TextView(context)
totalTimeView = TextView(context)
whenever(mockHolder.seekBar).thenReturn(seekBarView)
@@ -69,7 +74,7 @@
fun seekBarGone() {
// WHEN seek bar is disabled
val isEnabled = false
- val data = SeekBarViewModel.Progress(isEnabled, false, null, 0)
+ val data = SeekBarViewModel.Progress(isEnabled, false, false, null, 0)
observer.onChanged(data)
// THEN seek bar shows just a thin line with no text
assertThat(seekBarView.isEnabled()).isFalse()
@@ -84,7 +89,7 @@
fun seekBarVisible() {
// WHEN seek bar is enabled
val isEnabled = true
- val data = SeekBarViewModel.Progress(isEnabled, true, 3000, 12000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, false, 3000, 12000)
observer.onChanged(data)
// THEN seek bar is visible and thick
assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
@@ -96,7 +101,7 @@
@Test
fun seekBarProgress() {
// WHEN part of the track has been played
- val data = SeekBarViewModel.Progress(true, true, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, true, 3000, 120000)
observer.onChanged(data)
// THEN seek bar shows the progress
assertThat(seekBarView.progress).isEqualTo(3000)
@@ -112,7 +117,7 @@
fun seekBarDisabledWhenSeekNotAvailable() {
// WHEN seek is not available
val isSeekAvailable = false
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, 3000, 120000)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isFalse()
@@ -122,9 +127,29 @@
fun seekBarEnabledWhenSeekNotAvailable() {
// WHEN seek is available
val isSeekAvailable = true
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, 3000, 120000)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isTrue()
}
+
+ @Test
+ fun seekBarPlaying() {
+ // WHEN playing
+ val isPlaying = true
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, 3000, 120000)
+ observer.onChanged(data)
+ // THEN progress drawable is animating
+ verify(mockSquigglyProgress).animate = true
+ }
+
+ @Test
+ fun seekBarNotPlaying() {
+ // WHEN not playing
+ val isPlaying = false
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, 3000, 120000)
+ observer.onChanged(data)
+ // THEN progress drawable is not animating
+ verify(mockSquigglyProgress).animate = false
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
new file mode 100644
index 0000000..0787fd6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
@@ -0,0 +1,118 @@
+package com.android.systemui.media
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.LightingColorFilter
+import android.graphics.Paint
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SquigglyProgressTest : SysuiTestCase() {
+
+ private val colorFilter = LightingColorFilter(Color.RED, Color.BLUE)
+ private val strokeWidth = 5f
+ private val alpha = 128
+ private val tint = Color.GREEN
+
+ lateinit var squigglyProgress: SquigglyProgress
+ @Mock lateinit var canvas: Canvas
+ @Captor lateinit var wavePaintCaptor: ArgumentCaptor<Paint>
+ @Captor lateinit var linePaintCaptor: ArgumentCaptor<Paint>
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+
+ @Before
+ fun setup() {
+ squigglyProgress = SquigglyProgress()
+ squigglyProgress.waveLength = 30f
+ squigglyProgress.lineAmplitude = 10f
+ squigglyProgress.phaseSpeed = 8f
+ squigglyProgress.strokeWidth = strokeWidth
+ squigglyProgress.bounds = Rect(0, 0, 300, 30)
+ }
+
+ @Test
+ fun testDrawPathAndLine() {
+ squigglyProgress.draw(canvas)
+
+ verify(canvas).drawPath(any(), wavePaintCaptor.capture())
+ verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+ linePaintCaptor.capture())
+ }
+
+ @Test
+ fun testOnLevelChanged() {
+ assertThat(squigglyProgress.setLevel(5)).isFalse()
+ squigglyProgress.animate = true
+ assertThat(squigglyProgress.setLevel(4)).isTrue()
+ }
+
+ @Test
+ fun testStrokeWidth() {
+ squigglyProgress.draw(canvas)
+
+ verify(canvas).drawPath(any(), wavePaintCaptor.capture())
+ verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+ linePaintCaptor.capture())
+
+ assertThat(wavePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth)
+ assertThat(linePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth)
+ }
+
+ @Test
+ fun testAlpha() {
+ squigglyProgress.alpha = alpha
+ squigglyProgress.draw(canvas)
+
+ verify(canvas).drawPath(any(), wavePaintCaptor.capture())
+ verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+ linePaintCaptor.capture())
+
+ assertThat(squigglyProgress.alpha).isEqualTo(alpha)
+ assertThat(wavePaintCaptor.value.alpha).isEqualTo(alpha)
+ assertThat(linePaintCaptor.value.alpha).isEqualTo((alpha / 255f * DISABLED_ALPHA).toInt())
+ }
+
+ @Test
+ fun testColorFilter() {
+ squigglyProgress.colorFilter = colorFilter
+ squigglyProgress.draw(canvas)
+
+ verify(canvas).drawPath(any(), wavePaintCaptor.capture())
+ verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+ linePaintCaptor.capture())
+
+ assertThat(wavePaintCaptor.value.colorFilter).isEqualTo(colorFilter)
+ assertThat(linePaintCaptor.value.colorFilter).isEqualTo(colorFilter)
+ }
+
+ @Test
+ fun testTint() {
+ squigglyProgress.setTint(tint)
+ squigglyProgress.draw(canvas)
+
+ verify(canvas).drawPath(any(), wavePaintCaptor.capture())
+ verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+ linePaintCaptor.capture())
+
+ assertThat(wavePaintCaptor.value.color).isEqualTo(tint)
+ assertThat(linePaintCaptor.value.color).isEqualTo(tint)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 13e5821..380fa6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -37,15 +37,14 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.Before;
import org.junit.Test;
@@ -64,11 +63,10 @@
private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class);
private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
- private ShadeController mShadeController = mock(ShadeController.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
+ private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
- private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
@@ -77,17 +75,16 @@
private MediaOutputController mMediaOutputController;
private int mHeaderIconRes;
private IconCompat mIconCompat;
- private Drawable mAppSourceDrawable;
private CharSequence mHeaderTitle;
private CharSequence mHeaderSubtitle;
@Before
public void setUp() {
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotificationEntryManager, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
- mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
+ mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
}
@@ -178,15 +175,16 @@
class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
- MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) {
- super(context, mediaOutputController);
+ MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender,
+ MediaOutputController mediaOutputController) {
+ super(context, broadcastSender, mediaOutputController);
mAdapter = mMediaOutputBaseAdapter;
}
@Override
Drawable getAppSourceIcon() {
- return mAppSourceDrawable;
+ return null;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 6230700..d2dae74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -45,7 +45,6 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
@@ -57,7 +56,6 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.google.common.collect.ImmutableList;
@@ -96,10 +94,8 @@
private NearbyDevice mNearbyDevice2 = mock(NearbyDevice.class);
private MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class);
- private ShadeController mShadeController = mock(ShadeController.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
- private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
@@ -124,9 +120,9 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
mCachedBluetoothDeviceManager);
- mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
@@ -176,9 +172,9 @@
@Test
public void start_withoutPackageName_verifyMediaControllerInit() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mSpyContext, null,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
mMediaOutputController.start(mCb);
@@ -205,9 +201,9 @@
@Test
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mSpyContext, null,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
mMediaOutputController.start(mCb);
@@ -510,9 +506,9 @@
@Test
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mSpyContext, null,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index cb52e7c..db56f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -37,10 +37,10 @@
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.After;
import org.junit.Before;
@@ -61,8 +61,8 @@
// Mock
private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
- private final ShadeController mShadeController = mock(ShadeController.class);
private final ActivityStarter mStarter = mock(ActivityStarter.class);
+ private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
private final MediaDevice mMediaDevice = mock(MediaDevice.class);
private final NotificationEntryManager mNotificationEntryManager =
@@ -78,12 +78,12 @@
@Before
public void setUp() {
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotificationEntryManager, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputDialog = new MediaOutputDialog(mContext, false,
+ mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
mMediaOutputDialog.show();
@@ -129,7 +129,7 @@
// Check the visibility metric logging by creating a new MediaOutput dialog,
// and verify if the calling times increases.
public void onCreate_ShouldLogVisibility() {
- MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
+ MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
testDialog.show();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index f186f57..0cdde07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -28,17 +28,16 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.After;
import org.junit.Before;
@@ -59,14 +58,13 @@
// Mock
private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
- private ShadeController mShadeController = mock(ShadeController.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
+ private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
private MediaDevice mMediaDevice = mock(MediaDevice.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
- private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
@@ -77,12 +75,12 @@
@Before
public void setUp() {
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
- mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator,
+ mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotificationEntryManager, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager));
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
+ mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, mBroadcastSender,
mMediaOutputController);
mMediaOutputGroupDialog.show();
when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index afb63ab..a156820 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -37,6 +37,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.util.NotificationChannels;
@@ -59,7 +60,9 @@
// Test Instance.
mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class);
- mPowerNotificationWarnings = new PowerNotificationWarnings(mContext, starter);
+ BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class);
+ mPowerNotificationWarnings = new PowerNotificationWarnings(mContext, starter,
+ broadcastSender);
BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
mPowerNotificationWarnings.updateSnapshot(snapshot);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 19a9863..6b7e5b93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -20,21 +20,18 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
-import android.content.IntentFilter;
import android.os.Handler;
-import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
-import android.service.quicksettings.TileService;
+import android.service.quicksettings.IQSTileService;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -49,6 +46,7 @@
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -68,17 +66,25 @@
import java.util.ArrayList;
import java.util.Optional;
+import javax.inject.Provider;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TileServicesTest extends SysuiTestCase {
private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
+ private static final ComponentName TEST_COMPONENT =
+ ComponentName.unflattenFromString("pkg/.cls");
+
private TileServices mTileService;
+ private TestableLooper mTestableLooper;
private ArrayList<TileServiceManager> mManagers;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private CommandQueue mCommandQueue;
+ @Mock
private StatusBarIconController mStatusBarIconController;
@Mock
private QSFactoryImpl mQSFactory;
@@ -116,17 +122,20 @@
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(BluetoothController.class);
mManagers = new ArrayList<>();
+ mTestableLooper = TestableLooper.get(this);
when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
+ Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
+
QSTileHost host = new QSTileHost(mContext,
mStatusBarIconController,
mQSFactory,
- new Handler(),
- Looper.myLooper(),
+ provider.get(),
+ mTestableLooper.getLooper(),
mPluginManager,
mTunerService,
() -> mAutoTileManager,
@@ -140,8 +149,8 @@
mock(CustomTileStatePersister.class),
mTileServiceRequestControllerBuilder,
mTileLifecycleManagerFactory);
- mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
- mUserTracker, mKeyguardStateController);
+ mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
+ mUserTracker, mKeyguardStateController, mCommandQueue);
}
@After
@@ -152,24 +161,6 @@
}
@Test
- public void testActiveTileListenerRegisteredOnAllUsers() {
- ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
- verify(mBroadcastDispatcher).registerReceiver(any(), captor.capture(), any(), eq(
- UserHandle.ALL));
- assertTrue(captor.getValue().hasAction(TileService.ACTION_REQUEST_LISTENING));
- }
-
- @Test
- public void testBadComponentName_doesntCrash() {
- ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mBroadcastDispatcher).registerReceiver(captor.capture(), any(), any(), eq(
- UserHandle.ALL));
- Intent intent = new Intent(TileService.ACTION_REQUEST_LISTENING)
- .putExtra(Intent.EXTRA_COMPONENT_NAME, "abc");
- captor.getValue().onReceive(mContext, intent);
- }
-
- @Test
public void testRecalculateBindAllowance() {
// Add some fake tiles.
for (int i = 0; i < NUM_FAKES; i++) {
@@ -225,11 +216,36 @@
}
}
+ @Test
+ public void testRegisterCommand() {
+ verify(mCommandQueue).addCallback(any());
+ }
+
+ @Test
+ public void testRequestListeningStatusCommand() throws RemoteException {
+ ArgumentCaptor<CommandQueue.Callbacks> captor =
+ ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
+ verify(mCommandQueue).addCallback(captor.capture());
+
+ CustomTile mockTile = mock(CustomTile.class);
+ when(mockTile.getComponent()).thenReturn(TEST_COMPONENT);
+
+ TileServiceManager manager = mTileService.getTileWrapper(mockTile);
+ when(manager.isActiveTile()).thenReturn(true);
+ when(manager.getTileService()).thenReturn(mock(IQSTileService.class));
+
+ captor.getValue().requestTileServiceListeningState(TEST_COMPONENT);
+ mTestableLooper.processAllMessages();
+ verify(manager).setBindRequested(true);
+ verify(manager.getTileService()).onStartListening();
+ }
+
private class TestTileServices extends TileServices {
- TestTileServices(QSTileHost host, Looper looper,
+ TestTileServices(QSTileHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
- KeyguardStateController keyguardStateController) {
- super(host, looper, broadcastDispatcher, userTracker, keyguardStateController);
+ KeyguardStateController keyguardStateController, CommandQueue commandQueue) {
+ super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
+ commandQueue);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 067caa9..64a0a23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -44,6 +44,7 @@
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.ArgumentMatchers.eq
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -260,6 +261,49 @@
}
@Test
+ fun setDragAmount_setsKeyguardTransitionProgress() {
+ transitionController.dragDownAmount = 10f
+
+ verify(notificationPanelController).setKeyguardTransitionProgress(anyFloat(), anyInt())
+ }
+
+ @Test
+ fun setDragAmount_setsKeyguardAlphaBasedOnDistance() {
+ val alphaDistance = context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance)
+ transitionController.dragDownAmount = 10f
+
+ val expectedAlpha = 1 - 10f / alphaDistance
+ verify(notificationPanelController)
+ .setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
+ }
+
+ @Test
+ fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() {
+ val mediaTranslationY = 123
+ disableSplitShade()
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
+
+ transitionController.dragDownAmount = 10f
+
+ verify(notificationPanelController).setKeyguardTransitionProgress(anyFloat(), eq(0))
+ }
+
+ @Test
+ fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() {
+ val mediaTranslationY = 123
+ enableSplitShade()
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
+
+ transitionController.dragDownAmount = 10f
+
+ verify(notificationPanelController)
+ .setKeyguardTransitionProgress(anyFloat(), eq(mediaTranslationY))
+ }
+
+ @Test
fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
transitionController.dragDownAmount = 10f
@@ -276,8 +320,16 @@
}
private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
context.getOrCreateTestableResources().addOverride(
- R.bool.config_use_split_notification_shade, true
+ R.bool.config_use_split_notification_shade, enabled
)
configurationController.notifyConfigurationChanged()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 3a60c04..9f82a567 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -31,6 +31,7 @@
@Mock lateinit var notificationListContainer: NotificationListContainer
@Mock lateinit var headsUpManager: HeadsUpManagerPhone
@Mock lateinit var jankMonitor: InteractionJankMonitor
+ @Mock lateinit var onFinishAnimationCallback: Runnable
private lateinit var notificationTestHelper: NotificationTestHelper
private lateinit var notification: ExpandableNotificationRow
@@ -52,7 +53,8 @@
notificationListContainer,
headsUpManager,
notification,
- jankMonitor
+ jankMonitor,
+ onFinishAnimationCallback
)
}
@@ -61,7 +63,7 @@
}
@Test
- fun testHunIsRemovedIfWeDontAnimateLaunch() {
+ fun testHunIsRemovedAndCallbackIsInvokedIfWeDontAnimateLaunch() {
flagNotificationAsHun()
controller.onIntentStarted(willAnimate = false)
@@ -69,10 +71,11 @@
assertFalse(notification.entry.isExpandAnimationRunning)
verify(headsUpManager).removeNotification(
notificationKey, true /* releaseImmediately */, true /* animate */)
+ verify(onFinishAnimationCallback).run()
}
@Test
- fun testHunIsRemovedWhenAnimationIsCancelled() {
+ fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationIsCancelled() {
flagNotificationAsHun()
controller.onLaunchAnimationCancelled()
@@ -80,10 +83,11 @@
assertFalse(notification.entry.isExpandAnimationRunning)
verify(headsUpManager).removeNotification(
notificationKey, true /* releaseImmediately */, true /* animate */)
+ verify(onFinishAnimationCallback).run()
}
@Test
- fun testHunIsRemovedWhenAnimationEnds() {
+ fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationEnds() {
flagNotificationAsHun()
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
@@ -91,6 +95,7 @@
assertFalse(notification.entry.isExpandAnimationRunning)
verify(headsUpManager).removeNotification(
notificationKey, true /* releaseImmediately */, false /* animate */)
+ verify(onFinishAnimationCallback).run()
}
@Test
@@ -99,4 +104,4 @@
assertTrue(notification.entry.isExpandAnimationRunning)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 144eefb..699f77f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -170,11 +170,41 @@
}
@Test
+ fun testCancelAndReAddStickyNotification() {
+ whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(mEntry)
+ whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true, false)
+ whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+ addHUN(mEntry)
+ assertFalse(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+ mExecutor.advanceClockToLast()
+ mExecutor.runAllReady()
+ assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+ verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
+ verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
+ }
+
+ @Test
+ fun hunNotRemovedWhenExtensionCancelled() {
+ whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(mEntry)
+ whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
+ whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+ mNotifLifetimeExtender.cancelLifetimeExtension(mEntry)
+ mExecutor.advanceClockToLast()
+ mExecutor.runAllReady()
+ verify(mHeadsUpManager, times(0)).removeNotification(anyString(), any())
+ }
+
+ @Test
fun testCancelUpdatedStickyNotification() {
whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
addHUN(mEntry)
whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+ addHUN(mEntry)
mExecutor.advanceClockToLast()
mExecutor.runAllReady()
verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
@@ -305,6 +335,7 @@
mHuns.add(entry)
whenever(mHeadsUpManager.topEntry).thenReturn(entry)
mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
+ mNotifLifetimeExtender.cancelLifetimeExtension(entry)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index d094749..ca8529d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -16,18 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import static android.app.Notification.VISIBILITY_PUBLIC;
-import static android.app.Notification.VISIBILITY_SECRET;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.os.Handler;
import android.os.UserHandle;
@@ -39,20 +29,16 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
-import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -66,13 +52,13 @@
@Mock private Handler mMainHandler;
@Mock private KeyguardStateController mKeyguardStateController;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
@Mock private NotifPipeline mNotifPipeline;
+ @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
private NotificationEntry mEntry;
private NotifFilter mKeyguardFilter;
@@ -81,9 +67,9 @@
public void setup() {
MockitoAnnotations.initMocks(this);
KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator(
- mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
- mBroadcastDispatcher, mStatusBarStateController,
- mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider);
+ mStatusBarStateController,
+ mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider,
+ mKeyguardNotificationVisibilityProvider);
mEntry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
@@ -94,171 +80,4 @@
verify(mNotifPipeline, times(1)).addFinalizeFilter(filterCaptor.capture());
mKeyguardFilter = filterCaptor.getValue();
}
-
- @Test
- public void unfilteredState() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // THEN don't filter out the entry
- assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void keyguardNotShowing() {
- // GIVEN the lockscreen isn't showing
- setupUnfilteredState(mEntry);
- when(mKeyguardStateController.isShowing()).thenReturn(false);
-
- // THEN don't filter out the entry
- assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void doNotShowLockscreenNotifications() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN we shouldn't show any lockscreen notifications
- when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false);
-
- // THEN filter out the entry
- assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void lockdown() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in lockdown:
- when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true);
-
- // THEN filter out the entry
- assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void publicMode_settingsDisallow() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in public mode and settings are configured to disallow
- // notifications in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
- when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
- .thenReturn(false);
-
- // THEN filter out the entry
- assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void publicMode_notifDisallowed() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in public mode and settings are configured to disallow
- // notifications in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
- .setVisibilityOverride(VISIBILITY_SECRET).build());
-
- // THEN filter out the entry
- assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void doesNotExceedThresholdToShow() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification doesn't exceed the threshold to show on the lockscreen
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
- .setImportance(IMPORTANCE_MIN)
- .build());
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
-
- // THEN filter out the entry
- assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
- }
-
- @Test
- public void summaryExceedsThresholdToShow() {
- // GIVEN the notification doesn't exceed the threshold to show on the lockscreen
- // but it's part of a group (has a parent)
- final NotificationEntry entryWithParent = new NotificationEntryBuilder()
- .setUser(new UserHandle(NOTIF_USER_ID))
- .build();
-
- final GroupEntry parent = new GroupEntryBuilder()
- .setKey("test_group_key")
- .setSummary(new NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .build())
- .addChild(entryWithParent)
- .build();
-
- setupUnfilteredState(entryWithParent);
- entryWithParent.setRanking(new RankingBuilder()
- .setKey(entryWithParent.getKey())
- .setImportance(IMPORTANCE_MIN)
- .build());
-
- // WHEN its parent does exceed threshold tot show on the lockscreen
- when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true);
-
- // THEN don't filter out the entry
- assertFalse(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
-
- // WHEN its parent doesn't exceed threshold to show on lockscreen
- when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(false);
- modifyEntry(parent.getSummary(), builder -> builder
- .setImportance(IMPORTANCE_MIN)
- .done());
-
- // THEN filter out the entry
- assertTrue(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
- }
-
- /**
- * setup a state where the notification will not be filtered by the
- * KeyguardNotificationCoordinator when the keyguard is showing.
- */
- private void setupUnfilteredState(NotificationEntry entry) {
- // keyguard is showing
- when(mKeyguardStateController.isShowing()).thenReturn(true);
-
- // show notifications on the lockscreen
- when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true);
-
- // neither the current user nor the notification's user is in lockdown
- when(mLockscreenUserManager.getCurrentUserId()).thenReturn(CURR_USER_ID);
- when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(false);
- when(mKeyguardUpdateMonitor.isUserInLockdown(CURR_USER_ID)).thenReturn(false);
-
- // not in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(false);
- when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(false);
-
- // entry's ranking - should show on all lockscreens
- // + priority of the notification exceeds the threshold to be shown on the lockscreen
- entry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
- .setVisibilityOverride(VISIBILITY_PUBLIC)
- .setImportance(IMPORTANCE_HIGH)
- .build());
-
- // settings allows notifications in public mode
- when(mLockscreenUserManager.userAllowsNotificationsInPublic(CURR_USER_ID)).thenReturn(true);
- when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
- .thenReturn(true);
-
- // notification doesn't have a summary
-
- // notification is high priority, so it shouldn't be filtered
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
new file mode 100644
index 0000000..de9ea27
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.systemui.statusbar.notification.interruption;
+
+import static android.app.Notification.VISIBILITY_PUBLIC;
+import static android.app.Notification.VISIBILITY_SECRET;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
+ private static final int NOTIF_USER_ID = 0;
+ private static final int CURR_USER_ID = 1;
+
+ @Mock
+ private Handler mMainHandler;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private HighPriorityProvider mHighPriorityProvider;
+ @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
+ @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ @Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private BroadcastDispatcher mBroadcastDispatcher;
+
+ private NotificationEntry mEntry;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ // TODO refactor the test of KeyguardNotificationVisibilityProvider out
+ mKeyguardNotificationVisibilityProvider = spy(new KeyguardNotificationVisibilityProvider(
+ mContext,
+ mMainHandler,
+ mKeyguardStateController,
+ mLockscreenUserManager,
+ mKeyguardUpdateMonitor,
+ mHighPriorityProvider,
+ mStatusBarStateController,
+ mBroadcastDispatcher
+ ));
+
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .build();
+ }
+
+ @Test
+ public void unfilteredState() {
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't filter out the entry
+ assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void keyguardNotShowing() {
+ // GIVEN the lockscreen isn't showing
+ setupUnfilteredState(mEntry);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ // THEN don't filter out the entry
+ assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void doNotShowLockscreenNotifications() {
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN we shouldn't show any lockscreen notifications
+ when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false);
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void lockdown() {
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in lockdown:
+ when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true);
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void publicMode_settingsDisallow() {
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in public mode and settings are configured to disallow
+ // notifications in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+ when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+ .thenReturn(false);
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void publicMode_notifDisallowed() {
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in public mode and settings are configured to disallow
+ // notifications in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+ mEntry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void doesNotExceedThresholdToShow() {
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification doesn't exceed the threshold to show on the lockscreen
+ mEntry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setImportance(IMPORTANCE_MIN)
+ .build());
+ when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry));
+ }
+
+ @Test
+ public void summaryExceedsThresholdToShow() {
+ // GIVEN the notification doesn't exceed the threshold to show on the lockscreen
+ // but it's part of a group (has a parent)
+ final NotificationEntry entryWithParent = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .build();
+
+ final GroupEntry parent = new GroupEntryBuilder()
+ .setKey("test_group_key")
+ .setSummary(new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .build())
+ .addChild(entryWithParent)
+ .build();
+
+ setupUnfilteredState(entryWithParent);
+ entryWithParent.setRanking(new RankingBuilder()
+ .setKey(entryWithParent.getKey())
+ .setImportance(IMPORTANCE_MIN)
+ .build());
+
+ // WHEN its parent does exceed threshold tot show on the lockscreen
+ when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true);
+
+ // THEN don't filter out the entry
+ assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(entryWithParent));
+
+ // WHEN its parent doesn't exceed threshold to show on lockscreen
+ when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(false);
+ modifyEntry(parent.getSummary(), builder -> builder
+ .setImportance(IMPORTANCE_MIN)
+ .done());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(entryWithParent));
+ }
+
+ /**
+ * setup a state where the notification will not be filtered by the
+ * KeyguardNotificationCoordinator when the keyguard is showing.
+ */
+ private void setupUnfilteredState(NotificationEntry entry) {
+ // keyguard is showing
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ // show notifications on the lockscreen
+ when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true);
+
+ // neither the current user nor the notification's user is in lockdown
+ when(mLockscreenUserManager.getCurrentUserId()).thenReturn(CURR_USER_ID);
+ when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isUserInLockdown(CURR_USER_ID)).thenReturn(false);
+
+ // not in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(false);
+ when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(false);
+
+ // entry's ranking - should show on all lockscreens
+ // + priority of the notification exceeds the threshold to be shown on the lockscreen
+ entry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setVisibilityOverride(VISIBILITY_PUBLIC)
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+
+ // settings allows notifications in public mode
+ when(mLockscreenUserManager.userAllowsNotificationsInPublic(CURR_USER_ID)).thenReturn(true);
+ when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+ .thenReturn(true);
+
+ // notification doesn't have a summary
+
+ // notification is high priority, so it shouldn't be filtered
+ when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 2e1297b..8a2dc26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -30,6 +30,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -48,6 +51,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -86,6 +90,10 @@
BatteryController mBatteryController;
@Mock
Handler mMockHandler;
+ @Mock
+ NotifPipelineFlags mFlags;
+ @Mock
+ KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
@@ -104,8 +112,9 @@
mStatusBarStateController,
mHeadsUpManager,
mLogger,
- mMockHandler);
-
+ mMockHandler,
+ mFlags,
+ mKeyguardNotificationVisibilityProvider);
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
@@ -194,6 +203,16 @@
}
@Test
+ public void testDoNotRunFilterOnNewPipeline() {
+ when(mFlags.isNewPipelineEnabled()).thenReturn(true);
+ // WHEN this entry should be filtered out
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ mNotifInterruptionStateProvider.shouldHeadsUp(entry);
+ verify(mFlags, times(1)).isNewPipelineEnabled();
+ verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry));
+ }
+
+ @Test
public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException {
// GIVEN state for "heads up when awake" is true
ensureStateForHeadsUpWhenAwake();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java
index 3810783..c72f895 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java
@@ -131,6 +131,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.init.NotificationsController;
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -309,7 +310,9 @@
mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
mStatusBarStateController, mBatteryController, mHeadsUpManager,
mock(NotificationInterruptLogger.class),
- new Handler(TestableLooper.get(this).getLooper()));
+ new Handler(TestableLooper.get(this).getLooper()),
+ mock(NotifPipelineFlags.class),
+ mock(KeyguardNotificationVisibilityProvider.class));
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -994,9 +997,12 @@
BatteryController batteryController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
- Handler mainHandler) {
+ Handler mainHandler,
+ NotifPipelineFlags flags,
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter,
- batteryController, controller, headsUpManager, logger, mainHandler);
+ batteryController, controller, headsUpManager, logger, mainHandler,
+ flags, keyguardNotificationVisibilityProvider);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 6842295..4bac08e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -56,13 +56,11 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.test.filters.SmallTest;
@@ -389,8 +387,6 @@
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
- mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
- mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
mNotificationContainerParent.addView(mKeyguardStatusView);
mNotificationContainerParent.onFinishInflate();
when(mView.findViewById(R.id.notification_container_parent))
@@ -679,31 +675,6 @@
}
@Test
- public void testAllChildrenOfNotificationContainer_haveIds() {
- enableSplitShade(/* enabled= */ true);
- mNotificationContainerParent.removeAllViews();
- mNotificationContainerParent.addView(newViewWithId(1));
- mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
-
- mNotificationPanelViewController.updateResources();
-
- assertThat(mNotificationContainerParent.getChildAt(0).getId()).isEqualTo(1);
- assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID);
- }
-
- @Test
- public void testSinglePaneShadeLayout_isAlignedToParent() {
- enableSplitShade(/* enabled= */ false);
-
- mNotificationPanelViewController.updateResources();
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(ConstraintSet.PARENT_ID);
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(ConstraintSet.PARENT_ID);
- }
-
- @Test
public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
mStatusBarStateController.setState(KEYGUARD);
enableSplitShade(/* enabled= */ true);
@@ -752,46 +723,6 @@
}
@Test
- public void testSplitShadeLayout_isAlignedToGuideline() {
- enableSplitShade(/* enabled= */ true);
-
- mNotificationPanelViewController.updateResources();
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(R.id.qs_edge_guideline);
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(R.id.qs_edge_guideline);
- }
-
- @Test
- public void testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- enableSplitShade(/* enabled= */ true);
-
- mNotificationPanelViewController.updateResources();
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(10);
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0);
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
- .isEqualTo(0);
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).endMargin)
- .isEqualTo(10);
- }
-
- @Test
- public void testSinglePaneLayout_childrenHaveEqualMargins() {
- enableSplitShade(/* enabled= */ false);
-
- mNotificationPanelViewController.updateResources();
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(10);
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(10);
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
- .isEqualTo(10);
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).endMargin)
- .isEqualTo(10);
- }
-
- @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
@@ -1016,17 +947,6 @@
}
}
-
- private View newViewWithId(int id) {
- View view = new View(mContext);
- view.setId(id);
- ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- // required as cloning ConstraintSet fails if view doesn't have layout params
- view.setLayoutParams(layoutParams);
- return view;
- }
-
private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) {
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mNotificationContainerParent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index 5fb4144..83eabb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -2,8 +2,14 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowManagerPolicyConstants
+import androidx.annotation.AnyRes
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -13,6 +19,7 @@
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,8 +65,10 @@
lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
@Captor
lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor
+ lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
- private lateinit var notificationsQSContainerController: NotificationsQSContainerController
+ private lateinit var controller: NotificationsQSContainerController
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -68,36 +77,40 @@
fun setup() {
MockitoAnnotations.initMocks(this)
mContext.ensureTestableResources()
+ whenever(notificationsQSContainer.context).thenReturn(mContext)
whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
- notificationsQSContainerController = NotificationsQSContainerController(
+ controller = NotificationsQSContainerController(
notificationsQSContainer,
navigationModeController,
overviewProxyService,
featureFlags
)
- mContext.orCreateTestableResources
- .addOverride(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
-
- whenever(notificationsQSContainer.defaultNotificationsMarginBottom)
- .thenReturn(NOTIFICATIONS_MARGIN)
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+ overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
.thenReturn(GESTURES_NAVIGATION)
doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
doNothing().`when`(notificationsQSContainer)
.setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
- notificationsQSContainerController.init()
- notificationsQSContainerController.onViewAttached()
+ controller.init()
+ controller.onViewAttached()
navigationModeCallback = navigationModeCaptor.value
taskbarVisibilityCallback = taskbarVisibilityCaptor.value
windowInsetsCallback = windowInsetsCallbackCaptor.value
}
+ private fun overrideResource(@AnyRes id: Int, value: Any) {
+ mContext.orCreateTestableResources.addOverride(id, value)
+ }
+
@Test
fun testTaskbarVisibleInSplitShade() {
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
useNewFooter(false)
given(taskbarVisible = true,
@@ -115,7 +128,7 @@
@Test
fun testTaskbarVisibleInSplitShade_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
useNewFooter(true)
given(taskbarVisible = true,
@@ -136,7 +149,7 @@
@Test
fun testTaskbarNotVisibleInSplitShade() {
// when taskbar is not visible, it means we're on the home screen
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
useNewFooter(false)
given(taskbarVisible = false,
@@ -154,7 +167,7 @@
@Test
fun testTaskbarNotVisibleInSplitShade_newFooter() {
// when taskbar is not visible, it means we're on the home screen
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
useNewFooter(true)
given(taskbarVisible = false,
@@ -173,7 +186,7 @@
@Test
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
useNewFooter(false)
given(taskbarVisible = false,
@@ -190,7 +203,7 @@
@Test
fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
useNewFooter(true)
given(taskbarVisible = false,
@@ -209,7 +222,7 @@
@Test
fun testTaskbarVisibleInSinglePaneShade() {
- notificationsQSContainerController.splitShadeEnabled = false
+ disableSplitShade()
useNewFooter(false)
given(taskbarVisible = true,
@@ -225,7 +238,7 @@
@Test
fun testTaskbarVisibleInSinglePaneShade_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = false
+ disableSplitShade()
useNewFooter(true)
given(taskbarVisible = true,
@@ -243,7 +256,7 @@
@Test
fun testTaskbarNotVisibleInSinglePaneShade() {
- notificationsQSContainerController.splitShadeEnabled = false
+ disableSplitShade()
useNewFooter(false)
given(taskbarVisible = false,
@@ -264,7 +277,7 @@
@Test
fun testTaskbarNotVisibleInSinglePaneShade_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = false
+ disableSplitShade()
useNewFooter(true)
given(taskbarVisible = false,
@@ -285,8 +298,8 @@
@Test
fun testCustomizingInSinglePaneShade() {
- notificationsQSContainerController.splitShadeEnabled = false
- notificationsQSContainerController.setCustomizerShowing(true)
+ disableSplitShade()
+ controller.setCustomizerShowing(true)
useNewFooter(false)
// always sets spacings to 0
@@ -305,8 +318,8 @@
@Test
fun testCustomizingInSinglePaneShade_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = false
- notificationsQSContainerController.setCustomizerShowing(true)
+ disableSplitShade()
+ controller.setCustomizerShowing(true)
useNewFooter(true)
// always sets spacings to 0
@@ -325,8 +338,8 @@
@Test
fun testDetailShowingInSinglePaneShade() {
- notificationsQSContainerController.splitShadeEnabled = false
- notificationsQSContainerController.setDetailShowing(true)
+ disableSplitShade()
+ controller.setDetailShowing(true)
useNewFooter(false)
// always sets spacings to 0
@@ -345,8 +358,8 @@
@Test
fun testDetailShowingInSinglePaneShade_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = false
- notificationsQSContainerController.setDetailShowing(true)
+ disableSplitShade()
+ controller.setDetailShowing(true)
useNewFooter(true)
// always sets spacings to 0
@@ -365,8 +378,8 @@
@Test
fun testDetailShowingInSplitShade() {
- notificationsQSContainerController.splitShadeEnabled = true
- notificationsQSContainerController.setDetailShowing(true)
+ enableSplitShade()
+ controller.setDetailShowing(true)
useNewFooter(false)
given(taskbarVisible = false,
@@ -383,8 +396,8 @@
@Test
fun testDetailShowingInSplitShade_newFooter() {
- notificationsQSContainerController.splitShadeEnabled = true
- notificationsQSContainerController.setDetailShowing(true)
+ enableSplitShade()
+ controller.setDetailShowing(true)
useNewFooter(true)
given(taskbarVisible = false,
@@ -402,16 +415,106 @@
@Test
fun testNotificationsMarginBottomIsUpdated() {
Mockito.clearInvocations(notificationsQSContainer)
- notificationsQSContainerController.splitShadeEnabled = true
+ enableSplitShade()
verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
- whenever(notificationsQSContainer.defaultNotificationsMarginBottom).thenReturn(100)
- notificationsQSContainerController.updateMargins()
- notificationsQSContainerController.splitShadeEnabled = false
-
+ overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+ disableSplitShade()
verify(notificationsQSContainer).setNotificationsMarginBottom(100)
}
+ @Test
+ fun testSplitShadeLayout_isAlignedToGuideline() {
+ enableSplitShade()
+ controller.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(R.id.qs_edge_guideline)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+ .isEqualTo(R.id.qs_edge_guideline)
+ }
+
+ @Test
+ fun testSinglePaneLayout_childrenHaveEqualMargins() {
+ disableSplitShade()
+ controller.updateResources()
+ val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+ val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+ val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
+ val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
+ assertThat(qsStartMargin == qsEndMargin &&
+ notifStartMargin == notifEndMargin &&
+ qsStartMargin == notifStartMargin
+ ).isTrue()
+ }
+
+ @Test
+ fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+ enableSplitShade()
+ controller.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+ enableSplitShade()
+ controller.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+ disableSplitShade()
+ controller.updateResources()
+ val notificationPanelMarginHorizontal = context.resources
+ .getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_isAlignedToParent() {
+ disableSplitShade()
+ controller.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testAllChildrenOfNotificationContainer_haveIds() {
+ // set dimen to 0 to avoid triggering updating bottom spacing
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+ val container = NotificationsQuickSettingsContainer(context, null)
+ container.removeAllViews()
+ container.addView(newViewWithId(1))
+ container.addView(newViewWithId(View.NO_ID))
+ val controller = NotificationsQSContainerController(container, navigationModeController,
+ overviewProxyService, featureFlags)
+ controller.updateResources()
+
+ assertThat(container.getChildAt(0).id).isEqualTo(1)
+ assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ controller.updateResources()
+ }
+
private fun given(
taskbarVisible: Boolean,
navigationMode: Int,
@@ -458,4 +561,18 @@
private fun useNewFooter(useNewFooter: Boolean) {
whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter)
}
-}
\ No newline at end of file
+
+ private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+ return constraintSetCaptor.value.getConstraint(id).layout
+ }
+
+ private fun newViewWithId(id: Int): View {
+ val view = View(mContext)
+ view.id = id
+ val layoutParams = ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.layoutParams = layoutParams
+ return view
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 27179fd..d48ce8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -85,6 +85,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -188,10 +189,11 @@
when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
when(mOnUserInteractionCallback.getGroupSummaryToDismiss(mNotificationRow.getEntry()))
.thenReturn(null);
- when(mVisibilityProvider.obtain(anyString(), anyBoolean())).thenAnswer(
- invocation-> NotificationVisibility.obtain(invocation.getArgument(0), 0, 1, false));
- when(mVisibilityProvider.obtain(any(NotificationEntry.class), anyBoolean())).thenAnswer(
- invocation-> NotificationVisibility.obtain(
+ when(mVisibilityProvider.obtain(anyString(), anyBoolean()))
+ .thenAnswer(invocation -> NotificationVisibility.obtain(
+ invocation.getArgument(0), 0, 1, false));
+ when(mVisibilityProvider.obtain(any(NotificationEntry.class), anyBoolean()))
+ .thenAnswer(invocation -> NotificationVisibility.obtain(
invocation.<NotificationEntry>getArgument(0).getKey(), 0, 1, false));
HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
@@ -431,12 +433,18 @@
}
@Test
- public void testNotifActivityStarterEventSourceFinishEvent_postPanelCollapse() {
+ public void testNotifActivityStarterEventSourceFinishEvent_postPanelCollapse()
+ throws Exception {
NotifActivityLaunchEvents.Listener listener =
mock(NotifActivityLaunchEvents.Listener.class);
mLaunchEventsEmitter.registerListener(listener);
mNotificationActivityStarter
.onNotificationClicked(mNotificationRow.getEntry().getSbn(), mNotificationRow);
+ ArgumentCaptor<ActivityLaunchAnimator.Controller> controllerCaptor =
+ ArgumentCaptor.forClass(ActivityLaunchAnimator.Controller.class);
+ verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(
+ controllerCaptor.capture(), anyBoolean(), any(), any());
+ controllerCaptor.getValue().onIntentStarted(false);
verify(listener).onFinishLaunchNotifActivity(mNotificationRow.getEntry());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 91c347f..1caacb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -41,6 +41,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -83,6 +84,7 @@
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
@Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var falsingManager: FalsingManager
@@ -159,6 +161,7 @@
handler,
activityStarter,
broadcastDispatcher,
+ broadcastSender,
uiEventLogger,
falsingManager,
telephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 82880fe..78ee9e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -98,6 +98,7 @@
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -343,7 +344,9 @@
mock(BatteryController.class),
mock(HeadsUpManager.class),
mock(NotificationInterruptLogger.class),
- mock(Handler.class)
+ mock(Handler.class),
+ mock(NotifPipelineFlags.class),
+ mock(KeyguardNotificationVisibilityProvider.class)
);
when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index cc848bc3..fafe4b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -85,6 +85,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -309,7 +310,9 @@
mock(BatteryController.class),
mock(HeadsUpManager.class),
mock(NotificationInterruptLogger.class),
- mock(Handler.class)
+ mock(Handler.class),
+ mock(NotifPipelineFlags.class),
+ mock(KeyguardNotificationVisibilityProvider.class)
);
when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index e698f1e..a7f0dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -23,7 +23,9 @@
import android.service.dreams.IDreamManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -42,7 +44,9 @@
BatteryController batteryController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
- Handler mainHandler) {
+ Handler mainHandler,
+ NotifPipelineFlags flags,
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
super(contentResolver,
powerManager,
dreamManager,
@@ -52,7 +56,9 @@
statusBarStateController,
headsUpManager,
logger,
- mainHandler);
+ mainHandler,
+ flags,
+ keyguardNotificationVisibilityProvider);
mUseHeadsUp = true;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 7f10314..5ef1008 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -281,9 +281,9 @@
void onDoubleTapAndHold(int displayId);
- void requestImeLocked(AccessibilityServiceConnection connection);
+ void requestImeLocked(AbstractAccessibilityServiceConnection connection);
- void unbindImeLocked(AccessibilityServiceConnection connection);
+ void unbindImeLocked(AbstractAccessibilityServiceConnection connection);
}
public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
@@ -387,7 +387,6 @@
& AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
mRequestAccessibilityButton = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
- // TODO(b/218193835): request ime when ime flag is set and clean up when ime flag is unset
mRequestImeApis = (info.flags
& AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
}
@@ -439,6 +438,7 @@
// If the XML manifest had data to configure the service its info
// should be already set. In such a case update only the dynamically
// configurable properties.
+ boolean oldRequestIme = mRequestImeApis;
AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
if (oldInfo != null) {
oldInfo.updateDynamicallyConfigurableProperties(mIPlatformCompat, info);
@@ -447,6 +447,11 @@
setDynamicallyConfigurableProperties(info);
}
mSystemSupport.onClientChangeLocked(true);
+ if (!oldRequestIme && mRequestImeApis) {
+ mSystemSupport.requestImeLocked(this);
+ } else if (oldRequestIme && !mRequestImeApis) {
+ mSystemSupport.unbindImeLocked(this);
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0ea087d..249ee16 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4305,7 +4305,7 @@
}
@Override
- public void requestImeLocked(AccessibilityServiceConnection connection) {
+ public void requestImeLocked(AbstractAccessibilityServiceConnection connection) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::createSessionForConnection, this, connection));
mMainHandler.sendMessage(obtainMessage(
@@ -4313,12 +4313,12 @@
}
@Override
- public void unbindImeLocked(AccessibilityServiceConnection connection) {
+ public void unbindImeLocked(AbstractAccessibilityServiceConnection connection) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::unbindInputForConnection, this, connection));
}
- private void createSessionForConnection(AccessibilityServiceConnection connection) {
+ private void createSessionForConnection(AbstractAccessibilityServiceConnection connection) {
synchronized (mLock) {
if (mInputSessionRequested) {
connection.createImeSessionLocked();
@@ -4326,7 +4326,7 @@
}
}
- private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) {
+ private void bindAndStartInputForConnection(AbstractAccessibilityServiceConnection connection) {
synchronized (mLock) {
if (mInputBinding != null) {
connection.bindInputLocked(mInputBinding);
@@ -4336,7 +4336,7 @@
}
}
- private void unbindInputForConnection(AccessibilityServiceConnection connection) {
+ private void unbindInputForConnection(AbstractAccessibilityServiceConnection connection) {
InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId);
synchronized (mLock) {
connection.unbindInputLocked();
diff --git a/services/api/current.txt b/services/api/current.txt
index 45c0059..5a28802 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -38,7 +38,7 @@
package com.android.server.am {
public interface ActivityManagerLocal {
- method public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException;
+ method public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, @NonNull String, int) throws android.os.RemoteException;
method public boolean canStartForegroundService(int, int, @NonNull String);
}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
index b1f572d..ac2d1dd 100644
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
@@ -129,7 +129,7 @@
@Override
public void search(@NonNull SearchRequest searchRequest,
@NonNull ICloudSearchManagerCallback callBack) {
- searchRequest.setSource(
+ searchRequest.setCallerPackageName(
mContext.getPackageManager().getNameForUid(Binder.getCallingUid()));
runForUser("search", (service) -> {
synchronized (service.mLock) {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index b59cd4c..1a39ffa 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1166,9 +1166,17 @@
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
if (allowlist) {
- cm.updateMeteredNetworkAllowList(uid, enable);
+ if (enable) {
+ cm.addUidToMeteredNetworkAllowList(uid);
+ } else {
+ cm.removeUidFromMeteredNetworkAllowList(uid);
+ }
} else {
- cm.updateMeteredNetworkDenyList(uid, enable);
+ if (enable) {
+ cm.addUidToMeteredNetworkDenyList(uid);
+ } else {
+ cm.removeUidFromMeteredNetworkDenyList(uid);
+ }
}
synchronized (mRulesLock) {
if (enable) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4628a1f..7075ed3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -75,7 +75,6 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.content.res.ObbInfo;
import android.database.ContentObserver;
import android.net.Uri;
@@ -124,7 +123,6 @@
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.storage.ExternalStorageService;
-import android.sysprop.VoldProperties;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
@@ -1407,39 +1405,6 @@
private void handleDaemonConnected() {
initIfBootedAndConnected();
resetIfBootedAndConnected();
-
- // On an encrypted device we can't see system properties yet, so pull
- // the system locale out of the mount service.
- if ("".equals(VoldProperties.encrypt_progress().orElse(""))) {
- copyLocaleFromMountService();
- }
- }
-
- private void copyLocaleFromMountService() {
- String systemLocale;
- try {
- systemLocale = getField(StorageManager.SYSTEM_LOCALE_KEY);
- } catch (RemoteException e) {
- return;
- }
- if (TextUtils.isEmpty(systemLocale)) {
- return;
- }
-
- Slog.d(TAG, "Got locale " + systemLocale + " from mount service");
- Locale locale = Locale.forLanguageTag(systemLocale);
- Configuration config = new Configuration();
- config.setLocale(locale);
- try {
- ActivityManager.getService().updatePersistentConfigurationWithAttribution(config,
- mContext.getOpPackageName(), mContext.getAttributionTag());
- } catch (RemoteException e) {
- Slog.e(TAG, "Error setting system locale from mount service", e);
- }
-
- // Temporary workaround for http://b/17945169.
- Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service");
- SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
}
private final IVoldListener mListener = new IVoldListener.Stub() {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 40ab0c0..839cdc6 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -91,7 +91,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
-import com.android.internal.telephony.ICarrierPrivilegesListener;
+import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.ITelephonyRegistry;
@@ -153,7 +153,7 @@
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
- ICarrierPrivilegesListener carrierPrivilegesListener;
+ ICarrierPrivilegesCallback carrierPrivilegesCallback;
int callerUid;
int callerPid;
@@ -178,8 +178,8 @@
return (onOpportunisticSubscriptionsChangedListenerCallback != null);
}
- boolean matchCarrierPrivilegesListener() {
- return carrierPrivilegesListener != null;
+ boolean matchCarrierPrivilegesCallback() {
+ return carrierPrivilegesCallback != null;
}
boolean canReadCallLog() {
@@ -199,7 +199,7 @@
+ onSubscriptionsChangedListenerCallback
+ " onOpportunisticSubscriptionsChangedListenererCallback="
+ onOpportunisticSubscriptionsChangedListenerCallback
- + " carrierPrivilegesListener=" + carrierPrivilegesListener
+ + " carrierPrivilegesCallback=" + carrierPrivilegesCallback
+ " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
}
}
@@ -414,7 +414,9 @@
mPreciseDataConnectionStates;
/** Per-phoneId snapshot of privileged packages (names + UIDs). */
- private List<Pair<List<String>, int[]>> mCarrierPrivilegeStates;
+ @NonNull private List<Pair<List<String>, int[]>> mCarrierPrivilegeStates;
+ /** Per-phoneId of CarrierService (PackageName, UID) pair. */
+ @NonNull private List<Pair<String, Integer>> mCarrierServiceStates;
/**
* Support backward compatibility for {@link android.telephony.TelephonyDisplayInfo}.
@@ -705,6 +707,7 @@
cutListToSize(mPhysicalChannelConfigs, mNumPhones);
cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
cutListToSize(mCarrierPrivilegeStates, mNumPhones);
+ cutListToSize(mCarrierServiceStates, mNumPhones);
return;
}
@@ -746,6 +749,7 @@
mAllowedNetworkTypeValue[i] = -1;
mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
+ mCarrierServiceStates.add(i, new Pair<>(null, Process.INVALID_UID));
}
}
}
@@ -813,6 +817,7 @@
mDataEnabledReason = new int[numPhones];
mLinkCapacityEstimateLists = new ArrayList<>();
mCarrierPrivilegeStates = new ArrayList<>();
+ mCarrierServiceStates = new ArrayList<>();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -851,6 +856,7 @@
mAllowedNetworkTypeValue[i] = -1;
mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
+ mCarrierServiceStates.add(i, new Pair<>(null, Process.INVALID_UID));
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -2013,10 +2019,8 @@
return;
}
- ApnSetting apnSetting = preciseState.getApnSetting();
-
synchronized (mRecords) {
- if (validatePhoneId(phoneId)) {
+ if (validatePhoneId(phoneId) && preciseState.getApnSetting() != null) {
Pair<Integer, ApnSetting> key = Pair.create(preciseState.getTransportType(),
preciseState.getApnSetting());
PreciseDataConnectionState oldState = mPreciseDataConnectionStates.get(phoneId)
@@ -2786,16 +2790,16 @@
}
@Override
- public void addCarrierPrivilegesListener(
+ public void addCarrierPrivilegesCallback(
int phoneId,
- ICarrierPrivilegesListener callback,
- String callingPackage,
- String callingFeatureId) {
+ @NonNull ICarrierPrivilegesCallback callback,
+ @NonNull String callingPackage,
+ @NonNull String callingFeatureId) {
int callerUserId = UserHandle.getCallingUserId();
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
- "addCarrierPrivilegesListener");
+ "addCarrierPrivilegesCallback");
if (VDBG) {
log(
"listen carrier privs: E pkg=" + pii(callingPackage) + " phoneId=" + phoneId
@@ -2815,7 +2819,7 @@
if (r == null) return;
r.context = mContext;
- r.carrierPrivilegesListener = callback;
+ r.carrierPrivilegesCallback = callback;
r.callingPackage = callingPackage;
r.callingFeatureId = callingFeatureId;
r.callerUid = Binder.getCallingUid();
@@ -2827,10 +2831,18 @@
}
Pair<List<String>, int[]> state = mCarrierPrivilegeStates.get(phoneId);
+ Pair<String, Integer> carrierServiceState = mCarrierServiceStates.get(phoneId);
try {
- r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
- Collections.unmodifiableList(state.first),
- Arrays.copyOf(state.second, state.second.length));
+ if (r.matchCarrierPrivilegesCallback()) {
+ // Here, two callbacks are triggered in quick succession on the same binder.
+ // In typical case, we expect the callers to care about only one or the other.
+ r.carrierPrivilegesCallback.onCarrierPrivilegesChanged(
+ Collections.unmodifiableList(state.first),
+ Arrays.copyOf(state.second, state.second.length));
+
+ r.carrierPrivilegesCallback.onCarrierServiceChanged(carrierServiceState.first,
+ carrierServiceState.second);
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2838,12 +2850,12 @@
}
@Override
- public void removeCarrierPrivilegesListener(
- ICarrierPrivilegesListener callback, String callingPackage) {
+ public void removeCarrierPrivilegesCallback(
+ @NonNull ICarrierPrivilegesCallback callback, @NonNull String callingPackage) {
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
- "removeCarrierPrivilegesListener");
+ "removeCarrierPrivilegesCallback");
remove(callback.asBinder());
}
@@ -2868,13 +2880,13 @@
for (Record r : mRecords) {
// Listeners are per-slot, not per-subscription. This is to provide a stable
// view across SIM profile switches.
- if (!r.matchCarrierPrivilegesListener()
+ if (!r.matchCarrierPrivilegesCallback()
|| !idMatch(r, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phoneId)) {
continue;
}
try {
// Make sure even in-process listeners can't modify the values.
- r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+ r.carrierPrivilegesCallback.onCarrierPrivilegesChanged(
Collections.unmodifiableList(privilegedPackageNames),
Arrays.copyOf(privilegedUids, privilegedUids.length));
} catch (RemoteException ex) {
@@ -2885,6 +2897,34 @@
}
}
+ @Override
+ public void notifyCarrierServiceChanged(int phoneId, @Nullable String packageName, int uid) {
+ if (!checkNotifyPermission("notifyCarrierServiceChanged")) return;
+ if (!validatePhoneId(phoneId)) return;
+ if (VDBG) {
+ log("notifyCarrierServiceChanged: phoneId=" + phoneId
+ + ", package=" + pii(packageName) + ", uid=" + uid);
+ }
+
+ synchronized (mRecords) {
+ mCarrierServiceStates.set(
+ phoneId, new Pair<>(packageName, uid));
+ for (Record r : mRecords) {
+ // Listeners are per-slot, not per-subscription.
+ if (!r.matchCarrierPrivilegesCallback()
+ || !idMatch(r, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phoneId)) {
+ continue;
+ }
+ try {
+ r.carrierPrivilegesCallback.onCarrierServiceChanged(packageName, uid);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -2940,6 +2980,9 @@
pw.println(
"mCarrierPrivilegeState=<packages=" + pii(carrierPrivilegeState.first)
+ ", uids=" + Arrays.toString(carrierPrivilegeState.second) + ">");
+ Pair<String, Integer> carrierServiceState = mCarrierServiceStates.get(i);
+ pw.println("mCarrierServiceState=<package=" + pii(carrierServiceState.first)
+ + ", uid=" + carrierServiceState.second + ">");
pw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d4ad718..48e3264 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2722,7 +2722,7 @@
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
- String callingPackage, final int userId)
+ String sdkSandboxClientAppPackage, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
@@ -2807,8 +2807,9 @@
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
- isSdkSandboxService, sdkSandboxClientAppUid, resolvedType, callingPackage,
- callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant);
+ isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+ resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
+ isBindExternal, allowInstant);
if (res == null) {
return 0;
}
@@ -3228,14 +3229,14 @@
int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
- return retrieveServiceLocked(service, instanceName, false, 0, resolvedType, callingPackage,
- callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
- allowInstant);
+ return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
+ callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
+ isBindExternal, allowInstant);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
- String resolvedType,
+ String sdkSandboxClientAppPackage, String resolvedType,
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
@@ -3416,7 +3417,8 @@
: null;
r = new ServiceRecord(mAm, className, name, definingPackageName,
definingUid, filter, sInfo, callingFromFg, res,
- sdkSandboxProcessName, sdkSandboxClientAppUid);
+ sdkSandboxProcessName, sdkSandboxClientAppUid,
+ sdkSandboxClientAppPackage);
res.setService(r);
smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
@@ -4195,7 +4197,7 @@
if (r.isSdkSandbox) {
final int uid = Process.toSdkSandboxUid(r.sdkSandboxClientAppUid);
app = mAm.startSdkSandboxProcessLocked(procName, r.appInfo, true, intentFlags,
- hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid);
+ hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid, r.sdkSandboxClientAppPackage);
r.isolationHostProc = app;
} else {
app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 3226a2b..1d2c36b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -74,6 +74,8 @@
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
* @param clientAppUid Uid of the app for which the sdk sandbox process needs to be spawned.
+ * @param clientAppPackage Package of the app for which the sdk sandbox process needs to
+ * be spawned. This package must belong to the clientAppUid.
* @param processName Unique identifier for the service instance. Each unique name here will
* result in a different service instance being created. Identifiers must only contain
* ASCII letters, digits, underscores, and periods.
@@ -87,6 +89,7 @@
*/
@SuppressLint("RethrowRemoteException")
boolean bindSdkSandboxService(@NonNull Intent service, @NonNull ServiceConnection conn,
- int clientAppUid, @NonNull String processName, @Context.BindServiceFlags int flags)
+ int clientAppUid, @NonNull String clientAppPackage, @NonNull String processName,
+ @Context.BindServiceFlags int flags)
throws RemoteException;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9512e1d..0735648 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -36,6 +36,7 @@
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
@@ -1895,6 +1896,7 @@
0,
false,
0,
+ null,
new HostingRecord("system"));
app.setPersistent(true);
app.setPid(MY_PID);
@@ -2783,7 +2785,8 @@
false /* knownToBeDead */, 0 /* intentFlags */,
sNullHostingRecord /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY,
true /* allowWhileBooting */, true /* isolated */,
- uid, false /* supplemental */, 0 /* supplementalUid */,
+ uid, false /* isSdkSandbox */, 0 /* sdkSandboxUid */,
+ null /* sdkSandboxClientAppPackage */,
abiOverride, entryPoint, entryPointArgs, crashHandler);
return proc != null;
}
@@ -2792,11 +2795,12 @@
@GuardedBy("this")
final ProcessRecord startSdkSandboxProcessLocked(String processName,
ApplicationInfo info, boolean knownToBeDead, int intentFlags,
- HostingRecord hostingRecord, int zygotePolicyFlags, int sdkSandboxUid) {
+ HostingRecord hostingRecord, int zygotePolicyFlags, int sdkSandboxUid,
+ String sdkSandboxClientAppPackage) {
return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
hostingRecord, zygotePolicyFlags, false /* allowWhileBooting */,
false /* isolated */, 0 /* isolatedUid */,
- true /* isSdkSandbox */, sdkSandboxUid,
+ true /* isSdkSandbox */, sdkSandboxUid, sdkSandboxClientAppPackage,
null /* ABI override */, null /* entryPoint */,
null /* entryPointArgs */, null /* crashHandler */);
}
@@ -2808,7 +2812,8 @@
boolean isolated) {
return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
- false /* isSdkSandbox */, 0 /* sdkSandboxClientdAppUid */,
+ false /* isSdkSandbox */, 0 /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */,
null /* ABI override */, null /* entryPoint */,
null /* entryPointArgs */, null /* crashHandler */);
}
@@ -3780,7 +3785,7 @@
synchronized (mProcLock) {
mProcessList.killPackageProcessesLSP(packageName, appId, targetUserId,
ProcessList.SERVICE_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN, "kill background");
+ ApplicationExitInfo.SUBREASON_KILL_BACKGROUND, "kill background");
}
}
}
@@ -3810,7 +3815,7 @@
mProcessList.killPackageProcessesLSP(null /* packageName */, -1 /* appId */,
UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ,
ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
+ ApplicationExitInfo.SUBREASON_KILL_BACKGROUND,
"kill all background");
}
@@ -4352,7 +4357,7 @@
ProcessList.INVALID_ADJ, true, false, true,
false, true /* setRemoved */, false,
ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
+ ApplicationExitInfo.SUBREASON_STOP_APP,
"fully stop " + packageName + "/" + userId + " by user request");
}
@@ -4404,7 +4409,7 @@
evenPersistent, true /* setRemoved */, uninstalling,
packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
: ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
+ ApplicationExitInfo.SUBREASON_FORCE_STOP,
(packageName == null ? ("stop user " + userId) : ("stop " + packageName))
+ " due to " + reason);
}
@@ -4773,7 +4778,8 @@
thread.runIsolatedEntryPoint(
app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
} else if (instr2 != null) {
- thread.bindApplication(processName, appInfo, providerList,
+ thread.bindApplication(processName, appInfo, app.sdkSandboxClientAppPackage,
+ providerList,
instr2.mClass,
profilerInfo, instr2.mArguments,
instr2.mWatcher,
@@ -4787,8 +4793,8 @@
app.getDisabledCompatChanges(), serializedSystemFontMap,
app.getStartElapsedTime(), app.getStartUptime());
} else {
- thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
- null, null, null, testMode,
+ thread.bindApplication(processName, appInfo, app.sdkSandboxClientAppPackage,
+ providerList, null, profilerInfo, null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
@@ -5749,6 +5755,18 @@
owningUid, exported);
}
+ private void enforceDebuggable(ProcessRecord proc) {
+ if (!Build.IS_DEBUGGABLE && !proc.isDebuggable()) {
+ throw new SecurityException("Process not debuggable: " + proc.info.packageName);
+ }
+ }
+
+ private void enforceDebuggable(ApplicationInfo info) {
+ if (!Build.IS_DEBUGGABLE && (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+ throw new SecurityException("Process not debuggable: " + info.packageName);
+ }
+ }
+
/**
* As the only public entry point for permissions checking, this method
* can enforce the semantic that requesting a check on a null global
@@ -6553,7 +6571,7 @@
if (app == null) {
app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0,
- false, 0,
+ false, 0, null,
new HostingRecord("added application",
customProcess != null ? customProcess : info.processName));
updateLruProcessLocked(app, false, null);
@@ -6786,22 +6804,25 @@
}
void setTrackAllocationApp(ApplicationInfo app, String processName) {
- if (!Build.IS_DEBUGGABLE) {
- if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- throw new SecurityException("Process not debuggable: " + app.packageName);
- }
- }
+ enforceDebuggable(app);
synchronized (mProcLock) {
mTrackAllocationApp = processName;
}
}
- void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo) {
+ void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo,
+ ApplicationInfo sdkSandboxClientApp) {
synchronized (mAppProfiler.mProfilerLock) {
if (!Build.IS_DEBUGGABLE) {
boolean isAppDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
boolean isAppProfileable = app.isProfileableByShell();
+
+ if (sdkSandboxClientApp != null) {
+ isAppDebuggable |=
+ (sdkSandboxClientApp.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ isAppProfileable |= sdkSandboxClientApp.isProfileableByShell();
+ }
if (!isAppDebuggable && !isAppProfileable) {
throw new SecurityException("Process not debuggable, "
+ "and not profileable by shell: " + app.packageName);
@@ -6812,11 +6833,7 @@
}
void setNativeDebuggingAppLocked(ApplicationInfo app, String processName) {
- if (!Build.IS_DEBUGGABLE) {
- if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- throw new SecurityException("Process not debuggable: " + app.packageName);
- }
- }
+ enforceDebuggable(app);
mNativeDebuggingApp = processName;
}
@@ -12395,13 +12412,13 @@
String resolvedType, IServiceConnection connection, int flags, String instanceName,
String callingPackage, int userId) throws TransactionTooLargeException {
return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
- instanceName, false, 0, callingPackage, userId);
+ instanceName, false, 0, null, callingPackage, userId);
}
private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String instanceName,
- boolean isSdkSandboxService, int sdkSandboxClientdAppUid, String callingPackage,
- int userId)
+ boolean isSdkSandboxService, int sdkSandboxClientAppUid,
+ String sdkSandboxClientAppPackage, String callingPackage, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
@@ -12438,8 +12455,8 @@
}
synchronized (this) {
return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
- flags, instanceName, isSdkSandboxService, sdkSandboxClientdAppUid,
- callingPackage, userId);
+ flags, instanceName, isSdkSandboxService, sdkSandboxClientAppUid,
+ sdkSandboxClientAppPackage, callingPackage, userId);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -13636,7 +13653,7 @@
UserHandle.getAppId(extraUid),
userId, ProcessList.INVALID_ADJ,
ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
+ ApplicationExitInfo.SUBREASON_PACKAGE_UPDATE,
"change " + ssp);
}
}
@@ -15562,12 +15579,7 @@
throw new IllegalArgumentException("Unknown process: " + process);
}
- boolean isDebuggable = Build.IS_DEBUGGABLE;
- if (!isDebuggable) {
- if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- throw new SecurityException("Process not debuggable: " + proc);
- }
- }
+ enforceDebuggable(proc);
mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
@@ -15670,10 +15682,7 @@
throw new SecurityException("No process found for calling pid "
+ Binder.getCallingPid());
}
- if (!Build.IS_DEBUGGABLE
- && (proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- throw new SecurityException("Not running a debuggable build");
- }
+ enforceDebuggable(proc);
processName = proc.processName;
uid = proc.uid;
if (reportPackage != null && !proc.getPkgList().containsKey(reportPackage)) {
@@ -15884,13 +15893,7 @@
return false;
}
- if (!Build.IS_DEBUGGABLE) {
- if ((process.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- return false;
- }
- }
-
- return true;
+ return Build.IS_DEBUGGABLE || process.isDebuggable();
}
public boolean startBinderTracking() throws RemoteException {
@@ -16022,22 +16025,29 @@
@Override
public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
- int userAppUid, String processName, int flags) throws RemoteException {
+ int clientAppUid, String clientAppPackage, String processName, int flags)
+ throws RemoteException {
if (service == null) {
throw new IllegalArgumentException("intent is null");
}
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
+ if (clientAppPackage == null) {
+ throw new IllegalArgumentException("clientAppPackage is null");
+ }
if (processName == null) {
throw new IllegalArgumentException("processName is null");
}
if (service.getComponent() == null) {
throw new IllegalArgumentException("service must specify explicit component");
}
- if (!UserHandle.isApp(userAppUid)) {
+ if (!UserHandle.isApp(clientAppUid)) {
throw new IllegalArgumentException("uid is not within application range");
}
+ if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
+ throw new IllegalArgumentException("uid does not belong to provided package");
+ }
Handler handler = mContext.getMainThreadHandler();
@@ -16046,8 +16056,8 @@
return ActivityManagerService.this.bindServiceInstance(
mContext.getIApplicationThread(), mContext.getActivityToken(), service,
service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
- processName, /*isSupplementalProcessService*/ true, userAppUid,
- mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
+ processName, /*isSdkSandboxService*/ true, clientAppUid, clientAppPackage,
+ mContext.getOpPackageName(), UserHandle.getUserId(clientAppUid)) != 0;
}
@Override
@@ -16365,7 +16375,7 @@
if (pr.mState.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND
&& pr.mReceivers.numberOfCurReceivers() == 0) {
pr.killLocked("remove task", ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN, true);
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
} else {
// We delay killing processes that are not in the background or running a
// receiver.
@@ -16837,7 +16847,7 @@
}
if (profilerInfo != null) {
- setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo);
+ setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo, null);
}
wmLock.notify();
}
@@ -17625,11 +17635,7 @@
throw new IllegalArgumentException("Unknown process: " + process);
}
- if (!Build.IS_DEBUGGABLE) {
- if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- throw new SecurityException("Process not debuggable: " + proc);
- }
- }
+ enforceDebuggable(proc);
thread.attachAgent(path);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 28b807c..2b61e7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -343,6 +343,8 @@
return runSetStopUserOnSwitch(pw);
case "set-bg-abusive-uids":
return runSetBgAbusiveUids(pw);
+ case "list-bg-exemptions-config":
+ return runListBgExemptionsConfig(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -3292,6 +3294,19 @@
return 0;
}
+ private int runListBgExemptionsConfig(PrintWriter pw) throws RemoteException {
+ final ArraySet<String> sysConfigs = mInternal.mAppRestrictionController
+ .mBgRestrictionExemptioFromSysConfig;
+ if (sysConfigs != null) {
+ for (int i = 0, size = sysConfigs.size(); i < size; i++) {
+ pw.print(sysConfigs.valueAt(i));
+ pw.print(' ');
+ }
+ pw.println();
+ }
+ return 0;
+ }
+
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
Configuration config = mInterface.getConfiguration();
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index d6a4cf6..16a7283 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -58,7 +58,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
@@ -608,13 +607,7 @@
if (check != null) {
if ((pss * 1024) >= check && profile.getThread() != null
&& mMemWatchDumpProcName == null) {
- boolean isDebuggable = Build.IS_DEBUGGABLE;
- if (!isDebuggable) {
- if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
- isDebuggable = true;
- }
- }
- if (isDebuggable) {
+ if (Build.IS_DEBUGGABLE || proc.isDebuggable()) {
Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting");
startHeapDumpLPf(profile, false);
} else {
@@ -1702,7 +1695,8 @@
try {
if (start) {
stopProfilerLPf(null, 0);
- mService.setProfileApp(proc.info, proc.processName, profilerInfo);
+ mService.setProfileApp(proc.info, proc.processName, profilerInfo,
+ proc.isSdkSandbox ? proc.getClientInfoForSdkSandbox() : null);
mProfileData.setProfileProc(proc);
mProfileType = profileType;
ParcelFileDescriptor fd = profilerInfo.profileFd;
@@ -1886,8 +1880,7 @@
BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
if (ps == null || !ps.isActive()) {
st.batteryStats = ps = bstats.getProcessStatsLocked(
- bstats.mapUid(st.uid), st.name,
- elapsedRealtime, uptime);
+ st.uid, st.name, elapsedRealtime, uptime);
}
ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
}
@@ -2076,7 +2069,7 @@
if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) {
// We need to do a debuggable check here. See setAgentApp for why the check is
// postponed to here.
- if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ if (app.isDebuggable()) {
String agent = mAppAgentMap.get(processName);
// Do not overwrite already requested agent.
if (profilerInfo == null) {
@@ -2133,7 +2126,7 @@
if (preBindAgent != null) {
thread.attachAgent(preBindAgent);
}
- if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ if (app.isDebuggable()) {
thread.attachStartupAgents(app.info.dataDir);
}
return profilerInfo;
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 6e6cb87..dc8403a 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -182,7 +182,7 @@
/**
* Whether or not to show the foreground service manager on tapping notifications.
*/
- private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = false;
+ private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = true;
private final Context mContext;
private final HandlerThread mBgHandlerThread;
@@ -241,7 +241,7 @@
/**
* The pre-config packages that are exempted from the background restrictions.
*/
- private ArraySet<String> mBgRestrictionExemptioFromSysConfig;
+ ArraySet<String> mBgRestrictionExemptioFromSysConfig;
/**
* Lock specifically for bookkeeping around the carrier-privileged app set.
@@ -1595,8 +1595,8 @@
cancelRequestBgRestrictedIfNecessary(packageName, uid);
final Intent newIntent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
newIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mContext.sendBroadcastAsUser(newIntent,
- UserHandle.of(UserHandle.getUserId(uid)));
+ // Task manager runs in SystemUI, which is SYSTEM user only.
+ mContext.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM);
break;
}
}
@@ -1670,9 +1670,10 @@
if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER) {
final Intent intent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ // Task manager runs in SystemUI, which is SYSTEM user only.
pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- UserHandle.of(UserHandle.getUserId(uid)));
+ UserHandle.SYSTEM);
} else {
final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
@@ -1750,7 +1751,7 @@
SYSTEM_UID, UserHandle.getUserId(uid));
final String title = mContext.getString(titleRes);
final String message = mContext.getString(messageRes,
- ai != null ? pm.getText(packageName, ai.labelRes, ai) : packageName);
+ ai != null ? ai.loadLabel(pm) : packageName);
final Icon icon = ai != null ? Icon.createWithResource(packageName, ai.icon) : null;
postNotification(notificationId, packageName, uid, title, message, icon, pendingIntent,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 91822ac..eb7897b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -799,6 +799,7 @@
final BatteryUsageStatsQuery querySinceReset =
new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
+ .includeVirtualUids()
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
@@ -806,6 +807,7 @@
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
+ .includeVirtualUids()
.powerProfileModeledOnly()
.build();
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
@@ -821,6 +823,7 @@
final BatteryUsageStatsQuery queryBeforeReset =
new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
+ .includeVirtualUids()
.aggregateSnapshots(sessionStart, sessionEnd)
.build();
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ea63c08..ade44ea 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -21,6 +21,9 @@
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
@@ -29,6 +32,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -51,6 +55,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -64,6 +69,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.UserManager;
import android.permission.IPermissionManager;
import android.text.TextUtils;
import android.util.EventLog;
@@ -75,6 +81,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -250,12 +257,16 @@
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
r.enqueueClockTime = System.currentTimeMillis();
+ r.enqueueTime = SystemClock.uptimeMillis();
+ r.enqueueRealTime = SystemClock.elapsedRealtime();
mParallelBroadcasts.add(r);
enqueueBroadcastHelper(r);
}
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
r.enqueueClockTime = System.currentTimeMillis();
+ r.enqueueTime = SystemClock.uptimeMillis();
+ r.enqueueRealTime = SystemClock.elapsedRealtime();
mDispatcher.enqueueOrderedBroadcastLocked(r);
enqueueBroadcastHelper(r);
}
@@ -1121,6 +1132,7 @@
while (mParallelBroadcasts.size() > 0) {
r = mParallelBroadcasts.remove(0);
r.dispatchTime = SystemClock.uptimeMillis();
+ r.dispatchRealTime = SystemClock.elapsedRealtime();
r.dispatchClockTime = System.currentTimeMillis();
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -1292,6 +1304,7 @@
performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.userId);
+ logBootCompletedBroadcastCompletionLatencyIfPossible(r);
// Set this to null so that the reference
// (local and remote) isn't kept in the mBroadcastHistory.
r.resultTo = null;
@@ -1408,6 +1421,7 @@
r.receiverTime = SystemClock.uptimeMillis();
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
+ r.dispatchRealTime = SystemClock.elapsedRealtime();
r.dispatchClockTime = System.currentTimeMillis();
if (mLogLatencyMetrics) {
@@ -1866,6 +1880,59 @@
return null;
}
+ private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+ // Only log after last receiver.
+ // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+ // last BroadcastRecord of the split broadcast which has non-null resultTo.
+ final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+ if (r.nextReceiver < numReceivers) {
+ return;
+ }
+ final String action = r.intent.getAction();
+ int event = 0;
+ if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+ }
+ if (event != 0) {
+ final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
+ final int completeLatency = (int)
+ (SystemClock.uptimeMillis() - r.enqueueTime);
+ final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
+ final int completeRealLatency = (int)
+ (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+ int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+ // This method is called very infrequently, no performance issue we call
+ // LocalServices.getService() here.
+ final UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
+ final UserInfo userInfo = umInternal.getUserInfo(r.userId);
+ if (userInfo != null) {
+ userType = UserManager.getUserTypeForStatsd(userInfo.userType);
+ }
+ Slog.i(TAG_BROADCAST,
+ "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+ + action
+ + " dispatchLatency:" + dispatchLatency
+ + " completeLatency:" + completeLatency
+ + " dispatchRealLatency:" + dispatchRealLatency
+ + " completeRealLatency:" + completeRealLatency
+ + " receiversSize:" + r.receivers.size()
+ + " userId:" + r.userId
+ + " userType:" + (userInfo != null? userInfo.userType : null));
+ FrameworkStatsLog.write(
+ BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+ event,
+ dispatchLatency,
+ completeLatency,
+ dispatchRealLatency,
+ completeRealLatency,
+ r.userId,
+ userType);
+ }
+ }
+
private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
// STOPSHIP (217251579): Temporarily use temp-allowlist reason to identify
// push messages and record response events.
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 8b1e829..2ee32b6 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -84,11 +84,14 @@
boolean deferred;
int splitCount; // refcount for result callback, when split
int splitToken; // identifier for cross-BroadcastRecord refcount
+ long enqueueTime; // uptimeMillis when the broadcast was enqueued
+ long enqueueRealTime; // elapsedRealtime when the broadcast was enqueued
long enqueueClockTime; // the clock time the broadcast was enqueued
long dispatchTime; // when dispatch started on this set of receivers
+ long dispatchRealTime; // elapsedRealtime when the broadcast was dispatched
long dispatchClockTime; // the clock time the dispatch started
long receiverTime; // when current receiver started for timeouts.
- long finishTime; // when we finished the broadcast.
+ long finishTime; // when we finished the current receiver.
boolean timeoutExempt; // true if this broadcast is not subject to receiver timeouts
int resultCode; // current result code value.
String resultData; // current result data value.
@@ -169,7 +172,7 @@
pw.print(prefix); pw.print("dispatchTime=");
TimeUtils.formatDuration(dispatchTime, now, pw);
pw.print(" (");
- TimeUtils.formatDuration(dispatchClockTime-enqueueClockTime, pw);
+ TimeUtils.formatDuration(dispatchTime - enqueueTime, pw);
pw.print(" since enq)");
if (finishTime != 0) {
pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw);
@@ -324,8 +327,11 @@
delivery = from.delivery;
duration = from.duration;
resultTo = from.resultTo;
+ enqueueTime = from.enqueueTime;
+ enqueueRealTime = from.enqueueRealTime;
enqueueClockTime = from.enqueueClockTime;
dispatchTime = from.dispatchTime;
+ dispatchRealTime = from.dispatchRealTime;
dispatchClockTime = from.dispatchClockTime;
receiverTime = from.receiverTime;
finishTime = from.finishTime;
@@ -378,7 +384,9 @@
requiredPermissions, excludedPermissions, appOp, options, splitReceivers, resultTo,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
-
+ split.enqueueTime = this.enqueueTime;
+ split.enqueueRealTime = this.enqueueRealTime;
+ split.enqueueClockTime = this.enqueueClockTime;
split.splitToken = this.splitToken;
return split;
}
@@ -448,9 +456,11 @@
final BroadcastRecord br = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, appOp, options,
- uid2receiverList.valueAt(i), resultTo,
+ uid2receiverList.valueAt(i), null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
+ br.enqueueTime = this.enqueueTime;
+ br.enqueueRealTime = this.enqueueRealTime;
br.enqueueClockTime = this.enqueueClockTime;
ret.put(uid2receiverList.keyAt(i), br);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index e0d333e..7ed3dcf 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2561,7 +2561,7 @@
if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
&& state.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND) {
app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN, true);
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
success = false;
} else {
int processGroup;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 48ca59d..253686c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1718,8 +1718,16 @@
int runtimeFlags = 0;
boolean debuggableFlag = (app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- if (!debuggableFlag && app.isSdkSandbox) {
- debuggableFlag = isAppForSdkSandboxDebuggable(app);
+ boolean isProfileableByShell = app.info.isProfileableByShell();
+ boolean isProfileable = app.info.isProfileable();
+
+ if (app.isSdkSandbox) {
+ ApplicationInfo clientInfo = app.getClientInfoForSdkSandbox();
+ if (clientInfo != null) {
+ debuggableFlag |= (clientInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ isProfileableByShell |= clientInfo.isProfileableByShell();
+ isProfileable |= clientInfo.isProfileable();
+ }
}
if (debuggableFlag) {
@@ -1741,10 +1749,10 @@
if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 || mService.mSafeMode) {
runtimeFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
}
- if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0) {
+ if (isProfileableByShell) {
runtimeFlags |= Zygote.PROFILE_FROM_SHELL;
}
- if (app.info.isProfileable()) {
+ if (isProfileable) {
runtimeFlags |= Zygote.PROFILEABLE;
}
if ("1".equals(SystemProperties.get("debug.checkjni"))) {
@@ -1812,7 +1820,7 @@
}
String invokeWith = null;
- if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ if (debuggableFlag) {
// Debuggable apps may include a wrapper script with their library directory.
String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
@@ -1887,24 +1895,6 @@
}
}
- /** Return true if the client app for the SDK sandbox process is debuggable. */
- private boolean isAppForSdkSandboxDebuggable(ProcessRecord sandboxProcess) {
- // TODO (b/221004701) use client app process name
- final int appUid = Process.getAppUidForSdkSandboxUid(sandboxProcess.uid);
- IPackageManager pm = mService.getPackageManager();
- try {
- String[] packages = pm.getPackagesForUid(appUid);
- for (String aPackage : packages) {
- ApplicationInfo i = pm.getApplicationInfo(aPackage, 0, sandboxProcess.userId);
- if ((i.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
- return true;
- }
- }
- } catch (RemoteException e) {
- // shouldn't happen
- }
- return false;
- }
@GuardedBy("mService")
boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
@@ -2365,7 +2355,7 @@
ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
- boolean isSdkSandbox, int sdkSandboxUid,
+ boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage,
String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
long startTime = SystemClock.uptimeMillis();
ProcessRecord app;
@@ -2460,7 +2450,7 @@
if (app == null) {
checkSlow(startTime, "startProcess: creating new process record");
app = newProcessRecordLocked(info, processName, isolated, isolatedUid, isSdkSandbox,
- sdkSandboxUid, hostingRecord);
+ sdkSandboxUid, sdkSandboxClientAppPackage, hostingRecord);
if (app == null) {
Slog.w(TAG, "Failed making new process record for "
+ processName + "/" + info.uid + " isolated=" + isolated);
@@ -2956,7 +2946,7 @@
@GuardedBy("mService")
ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
boolean isolated, int isolatedUid, boolean isSdkSandbox, int sdkSandboxUid,
- HostingRecord hostingRecord) {
+ String sdkSandboxClientAppPackage, HostingRecord hostingRecord) {
String proc = customProcess != null ? customProcess : info.processName;
final int userId = UserHandle.getUserId(info.uid);
int uid = info.uid;
@@ -2992,6 +2982,7 @@
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
}
final ProcessRecord r = new ProcessRecord(mService, info, proc, uid,
+ sdkSandboxClientAppPackage,
hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f7cc3d7..b4ff870 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -27,6 +27,7 @@
import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ProcessInfo;
import android.content.pm.VersionedPackage;
import android.content.res.CompatibilityInfo;
@@ -84,6 +85,8 @@
final int uid; // uid of process; may be different from 'info' if isolated
final int userId; // user of process.
final String processName; // name of the process
+ final String sdkSandboxClientAppPackage; // if this is an sdk sandbox process, name of the
+ // app package for which it is running
/**
* Overall state of process's uid.
@@ -493,11 +496,12 @@
ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
int _uid) {
- this(_service, _info, _processName, _uid, -1, null);
+ this(_service, _info, _processName, _uid, null, -1, null);
}
ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
- int _uid, int _definingUid, String _definingProcessName) {
+ int _uid, String _sdkSandboxClientAppPackage, int _definingUid,
+ String _definingProcessName) {
mService = _service;
mProcLock = _service.mProcLock;
info = _info;
@@ -530,6 +534,7 @@
uid = _uid;
userId = UserHandle.getUserId(_uid);
processName = _processName;
+ sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
mPersistent = false;
mRemoved = false;
mProfile = new ProcessProfileRecord(this);
@@ -861,6 +866,29 @@
return mDebugging;
}
+ @Nullable
+ public ApplicationInfo getClientInfoForSdkSandbox() {
+ if (!isSdkSandbox || sdkSandboxClientAppPackage == null) {
+ throw new IllegalStateException(
+ "getClientInfoForSdkSandbox called for non-sandbox process"
+ );
+ }
+ PackageManagerInternal pm = mService.getPackageManagerInternal();
+ return pm.getApplicationInfo(
+ sdkSandboxClientAppPackage, /* flags */0, Process.SYSTEM_UID, userId);
+ }
+
+ public boolean isDebuggable() {
+ if ((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ return true;
+ }
+ if (isSdkSandbox) {
+ ApplicationInfo clientInfo = getClientInfoForSdkSandbox();
+ return clientInfo != null && (clientInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ }
+ return false;
+ }
+
@GuardedBy("mService")
void setDebugging(boolean debugging) {
mDebugging = debugging;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index c53d4d6..795311f 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -96,6 +96,8 @@
final long createRealTime; // when this service was created
final boolean isSdkSandbox; // whether this is a sdk sandbox service
final int sdkSandboxClientAppUid; // the app uid for which this sdk sandbox service is running
+ final String sdkSandboxClientAppPackage; // the app package for which this sdk sandbox service
+ // is running
final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
= new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
// All active bindings to the service.
@@ -573,13 +575,14 @@
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
Runnable restarter) {
this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
- restarter, null, 0);
+ restarter, null, 0, null);
}
ServiceRecord(ActivityManagerService ams, ComponentName name,
ComponentName instanceName, String definingPackageName, int definingUid,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
- Runnable restarter, String sdkSandboxProcessName, int sdkSandboxClientAppUid) {
+ Runnable restarter, String sdkSandboxProcessName, int sdkSandboxClientAppUid,
+ String sdkSandboxClientAppPackage) {
this.ams = ams;
this.name = name;
this.instanceName = instanceName;
@@ -590,8 +593,9 @@
serviceInfo = sInfo;
appInfo = sInfo.applicationInfo;
packageName = sInfo.applicationInfo.packageName;
- isSdkSandbox = sdkSandboxProcessName != null;
+ this.isSdkSandbox = sdkSandboxProcessName != null;
this.sdkSandboxClientAppUid = sdkSandboxClientAppUid;
+ this.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
processName = sInfo.processName + ":" + instanceName.getClassName();
} else if (sdkSandboxProcessName != null) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ad17655..8795347 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2618,7 +2618,7 @@
if (getStartedUserState(userId) == null) {
return false;
}
- if (!mInjector.getUserManager().isCredentialSharedWithParent(userId)) {
+ if (!mInjector.getUserManager().isCredentialSharableWithParent(userId)) {
return false;
}
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
index b0a389d..a76eb8f 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
@@ -52,6 +53,7 @@
mContext,
new GameClassifierImpl(mContext.getPackageManager()),
ActivityManager.getService(),
+ LocalServices.getService(ActivityManagerInternal.class),
ActivityTaskManager.getService(),
(WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
LocalServices.getService(WindowManagerInternal.class),
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index faf5c38..e920523 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -21,9 +21,11 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
+import android.app.IProcessObserver;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.content.Context;
@@ -45,6 +47,7 @@
import android.service.games.IGameSession;
import android.service.games.IGameSessionController;
import android.service.games.IGameSessionService;
+import android.text.TextUtils;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost.SurfacePackage;
@@ -143,12 +146,42 @@
GameServiceProviderInstanceImpl.this.onTaskFocusChanged(taskId, focused);
});
}
+ };
- // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider
- // to only when the associated task is running. Right now it is possible for a task to
- // move into the background and for all associated processes to die and for the Game Session
- // provider's GameSessionService to continue to be running. Ideally we could unbind the
- // service when this happens.
+ /**
+ * The TaskStackListener declared above gives us good visibility into game task lifecycle.
+ * However, it is possible for the Android system to kill all the processes associated with a
+ * game task (e.g., when the system is under memory pressure or reaches a background process
+ * limit). When this happens, the game task remains (and no TaskStackListener callbacks are
+ * invoked), but we would nonetheless want to destroy a game session associated with the task
+ * if this were to happen.
+ *
+ * This process observer gives us visibility into process lifecycles and lets us track all the
+ * processes associated with each package so that any game sessions associated with the package
+ * are destroyed if the process count for a given package reaches zero (most packages will
+ * have at most one task). If processes for a given package are started up again, the destroyed
+ * game sessions will be re-created.
+ */
+ private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
+ @Override
+ public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+ // This callback is used to track how many processes are running for a given package.
+ // Then, when a process dies, we will know if it was the only process running for that
+ // package and the associated game sessions should be destroyed.
+ mBackgroundExecutor.execute(() -> {
+ GameServiceProviderInstanceImpl.this.onForegroundActivitiesChanged(pid);
+ });
+ }
+
+ @Override
+ public void onProcessDied(int pid, int uid) {
+ mBackgroundExecutor.execute(() -> {
+ GameServiceProviderInstanceImpl.this.onProcessDied(pid);
+ });
+ }
+
+ @Override
+ public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {}
};
private final IGameServiceController mGameServiceController =
@@ -192,6 +225,7 @@
private final Context mContext;
private final GameClassifier mGameClassifier;
private final IActivityManager mActivityManager;
+ private final ActivityManagerInternal mActivityManagerInternal;
private final IActivityTaskManager mActivityTaskManager;
private final WindowManagerService mWindowManagerService;
private final WindowManagerInternal mWindowManagerInternal;
@@ -203,6 +237,12 @@
private final ConcurrentHashMap<Integer, GameSessionRecord> mGameSessions =
new ConcurrentHashMap<>();
@GuardedBy("mLock")
+ private final ConcurrentHashMap<Integer, String> mPidToPackageMap = new ConcurrentHashMap<>();
+ @GuardedBy("mLock")
+ private final ConcurrentHashMap<String, Integer> mPackageNameToProcessCountMap =
+ new ConcurrentHashMap<>();
+
+ @GuardedBy("mLock")
private volatile boolean mIsRunning;
GameServiceProviderInstanceImpl(
@@ -211,6 +251,7 @@
@NonNull Context context,
@NonNull GameClassifier gameClassifier,
@NonNull IActivityManager activityManager,
+ @NonNull ActivityManagerInternal activityManagerInternal,
@NonNull IActivityTaskManager activityTaskManager,
@NonNull WindowManagerService windowManagerService,
@NonNull WindowManagerInternal windowManagerInternal,
@@ -222,6 +263,7 @@
mContext = context;
mGameClassifier = gameClassifier;
mActivityManager = activityManager;
+ mActivityManagerInternal = activityManagerInternal;
mActivityTaskManager = activityTaskManager;
mWindowManagerService = windowManagerService;
mWindowManagerInternal = windowManagerInternal;
@@ -263,6 +305,12 @@
Slog.w(TAG, "Failed to register task stack listener", e);
}
+ try {
+ mActivityManager.registerProcessObserver(mProcessObserver);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to register process observer", e);
+ }
+
mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener);
}
@@ -274,6 +322,12 @@
mIsRunning = false;
try {
+ mActivityManager.unregisterProcessObserver(mProcessObserver);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to unregister process observer", e);
+ }
+
+ try {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to unregister task stack listener", e);
@@ -596,6 +650,126 @@
}
}
+ private void onForegroundActivitiesChanged(int pid) {
+ synchronized (mLock) {
+ onForegroundActivitiesChangedLocked(pid);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onForegroundActivitiesChangedLocked(int pid) {
+ if (mPidToPackageMap.containsKey(pid)) {
+ // We are already tracking this pid, nothing to do.
+ return;
+ }
+
+ final String packageName = mActivityManagerInternal.getPackageNameByPid(pid);
+ if (TextUtils.isEmpty(packageName)) {
+ // Game processes should always have a package name.
+ return;
+ }
+
+ if (!gameSessionExistsForPackageNameLocked(packageName)) {
+ // We only need to track processes for tasks with game session records.
+ return;
+ }
+
+ mPidToPackageMap.put(pid, packageName);
+ final int processCountForPackage = mPackageNameToProcessCountMap.getOrDefault(packageName,
+ 0) + 1;
+ mPackageNameToProcessCountMap.put(packageName, processCountForPackage);
+
+ if (DEBUG) {
+ Slog.d(TAG, "onForegroundActivitiesChangedLocked: tracking pid " + pid + ", for "
+ + packageName + ". Process count for package: " + processCountForPackage);
+ }
+
+ // If there are processes for the package, we may need to re-create game sessions
+ // that are associated with the package
+ if (processCountForPackage > 0) {
+ recreateEndedGameSessionsLocked(packageName);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void recreateEndedGameSessionsLocked(String packageName) {
+ for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
+ if (gameSessionRecord.isGameSessionEndedForProcessDeath() && packageName.equals(
+ gameSessionRecord.getComponentName().getPackageName())) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "recreateGameSessionsLocked(): re-creating game session for: "
+ + packageName + " with taskId: "
+ + gameSessionRecord.getTaskId());
+ }
+
+ final int taskId = gameSessionRecord.getTaskId();
+ mGameSessions.put(taskId, GameSessionRecord.awaitingGameSessionRequest(taskId,
+ gameSessionRecord.getComponentName()));
+ createGameSessionLocked(gameSessionRecord.getTaskId());
+ }
+ }
+ }
+
+ private void onProcessDied(int pid) {
+ synchronized (mLock) {
+ onProcessDiedLocked(pid);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onProcessDiedLocked(int pid) {
+ final String packageName = mPidToPackageMap.remove(pid);
+ if (packageName == null) {
+ // We weren't tracking this process.
+ return;
+ }
+
+ final Integer oldProcessCountForPackage = mPackageNameToProcessCountMap.get(packageName);
+ if (oldProcessCountForPackage == null) {
+ // This should never happen; we should have a process count for all tracked packages.
+ Slog.w(TAG, "onProcessDiedLocked(): Missing process count for package");
+ return;
+ }
+
+ final int processCountForPackage = oldProcessCountForPackage - 1;
+ mPackageNameToProcessCountMap.put(packageName, processCountForPackage);
+
+ // If there are no more processes for the game, then we will terminate any game sessions
+ // running for the package.
+ if (processCountForPackage <= 0) {
+ endGameSessionsForPackageLocked(packageName);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void endGameSessionsForPackageLocked(String packageName) {
+ for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
+ if (gameSessionRecord.getGameSession() != null && packageName.equals(
+ gameSessionRecord.getComponentName().getPackageName())) {
+ if (DEBUG) {
+ Slog.d(TAG, "endGameSessionsForPackageLocked(): No more processes for "
+ + packageName + ", ending game session with taskId: "
+ + gameSessionRecord.getTaskId());
+ }
+ mGameSessions.put(gameSessionRecord.getTaskId(),
+ gameSessionRecord.withGameSessionEndedOnProcessDeath());
+ destroyGameSessionFromRecordLocked(gameSessionRecord);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean gameSessionExistsForPackageNameLocked(String packageName) {
+ for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
+ if (packageName.equals(gameSessionRecord.getComponentName().getPackageName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@Nullable
private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) {
RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId);
diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java
index a241812..74e538e 100644
--- a/services/core/java/com/android/server/app/GameSessionRecord.java
+++ b/services/core/java/com/android/server/app/GameSessionRecord.java
@@ -35,6 +35,10 @@
// A Game Session is created and attached.
// GameSessionRecord.getGameSession() != null.
GAME_SESSION_ATTACHED,
+ // A Game Session did exist for a given game task but was destroyed because the last process
+ // for the game died.
+ // GameSessionRecord.getGameSession() == null.
+ GAME_SESSION_ENDED_PROCESS_DEATH,
}
private final int mTaskId;
@@ -99,6 +103,20 @@
}
@NonNull
+ public GameSessionRecord withGameSessionEndedOnProcessDeath() {
+ return new GameSessionRecord(
+ mTaskId,
+ State.GAME_SESSION_ENDED_PROCESS_DEATH,
+ mRootComponentName,
+ /* gameSession=*/ null,
+ /* surfacePackage=*/ null);
+ }
+
+ public boolean isGameSessionEndedForProcessDeath() {
+ return mState == State.GAME_SESSION_ENDED_PROCESS_DEATH;
+ }
+
+ @NonNull
public int getTaskId() {
return mTaskId;
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0f4648a..52834cb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -373,7 +373,7 @@
LockscreenCredential profileUserPassword) {
if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + profileUserId);
// Only for profiles that shares credential with parent
- if (!isCredentialSharedWithParent(profileUserId)) {
+ if (!isCredentialSharableWithParent(profileUserId)) {
return;
}
// Do not tie profile when work challenge is enabled
@@ -789,7 +789,7 @@
private void ensureProfileKeystoreUnlocked(int userId) {
final KeyStore ks = KeyStore.getInstance();
if (ks.state(userId) == KeyStore.State.LOCKED
- && isCredentialSharedWithParent(userId)
+ && isCredentialSharableWithParent(userId)
&& hasUnifiedChallenge(userId)) {
Slog.i(TAG, "Profile got unlocked, will unlock its keystore");
// If boot took too long and the password in vold got expired, parent keystore will
@@ -810,7 +810,7 @@
// Hide notification first, as tie managed profile lock takes time
hideEncryptionNotification(new UserHandle(userId));
- if (isCredentialSharedWithParent(userId)) {
+ if (isCredentialSharableWithParent(userId)) {
tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
}
@@ -1077,7 +1077,7 @@
final int userCount = users.size();
for (int i = 0; i < userCount; i++) {
UserInfo user = users.get(i);
- if (isCredentialSharedWithParent(user.id)
+ if (isCredentialSharableWithParent(user.id)
&& !getSeparateProfileChallengeEnabledInternal(user.id)) {
success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
PROFILE_KEY_NAME_ENCRYPT + user.id);
@@ -1471,7 +1471,7 @@
Thread.currentThread().interrupt();
}
- if (isCredentialSharedWithParent(userId)) {
+ if (isCredentialSharableWithParent(userId)) {
if (!hasUnifiedChallenge(userId)) {
mBiometricDeferredQueue.processPendingLockoutResets();
}
@@ -1480,7 +1480,7 @@
for (UserInfo profile : mUserManager.getProfiles(userId)) {
if (profile.id == userId) continue;
- if (!isCredentialSharedWithParent(profile.id)) continue;
+ if (!isCredentialSharableWithParent(profile.id)) continue;
if (hasUnifiedChallenge(profile.id)) {
if (mUserManager.isUserRunning(profile.id)) {
@@ -1517,7 +1517,7 @@
}
private Map<Integer, LockscreenCredential> getDecryptedPasswordsForAllTiedProfiles(int userId) {
- if (isCredentialSharedWithParent(userId)) {
+ if (isCredentialSharableWithParent(userId)) {
return null;
}
Map<Integer, LockscreenCredential> result = new ArrayMap<>();
@@ -1525,7 +1525,7 @@
final int size = profiles.size();
for (int i = 0; i < size; i++) {
final UserInfo profile = profiles.get(i);
- if (!isCredentialSharedWithParent(profile.id)) {
+ if (!isCredentialSharableWithParent(profile.id)) {
continue;
}
final int profileUserId = profile.id;
@@ -1560,7 +1560,7 @@
*/
private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
Map<Integer, LockscreenCredential> profilePasswordMap) {
- if (isCredentialSharedWithParent(userId)) {
+ if (isCredentialSharableWithParent(userId)) {
return;
}
final boolean isSecure = isUserSecure(userId);
@@ -1569,7 +1569,7 @@
for (int i = 0; i < size; i++) {
final UserInfo profile = profiles.get(i);
final int profileUserId = profile.id;
- if (isCredentialSharedWithParent(profileUserId)) {
+ if (isCredentialSharableWithParent(profileUserId)) {
if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
continue;
}
@@ -1596,12 +1596,12 @@
}
private boolean isProfileWithUnifiedLock(int userId) {
- return isCredentialSharedWithParent(userId)
+ return isCredentialSharableWithParent(userId)
&& !getSeparateProfileChallengeEnabledInternal(userId);
}
private boolean isProfileWithSeparatedLock(int userId) {
- return isCredentialSharedWithParent(userId)
+ return isCredentialSharableWithParent(userId)
&& getSeparateProfileChallengeEnabledInternal(userId);
}
@@ -1719,7 +1719,7 @@
setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
notifyPasswordChanged(credential, userId);
}
- if (isCredentialSharedWithParent(userId)) {
+ if (isCredentialSharableWithParent(userId)) {
// Make sure the profile doesn't get locked straight after setting work challenge.
setDeviceUnlockedForUser(userId);
}
@@ -1734,7 +1734,7 @@
/**
* @param savedCredential if the user is a profile with
- * {@link UserManager#isCredentialSharedWithParent()} with unified challenge and
+ * {@link UserManager#isCredentialSharableWithParent()} with unified challenge and
* savedCredential is empty, LSS will try to re-derive the profile password internally.
* TODO (b/80170828): Fix this so profile password is always passed in.
* @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
@@ -1800,7 +1800,10 @@
}
private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
- updateEncryptionPasswordIfNeeded(newCredential, userHandle);
+ if (userHandle == UserHandle.USER_SYSTEM && isDeviceEncryptionEnabled() &&
+ shouldEncryptWithCredentials() && newCredential.isNone()) {
+ setCredentialRequiredToDecrypt(false);
+ }
if (newCredential.isPattern()) {
setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
}
@@ -1809,26 +1812,6 @@
}
/**
- * Update device encryption password if calling user is USER_SYSTEM and device supports
- * encryption.
- */
- private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
- // Update the device encryption password.
- if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
- return;
- }
- if (!shouldEncryptWithCredentials()) {
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- return;
- }
- if (credential.isNone()) {
- // Set the encryption password to default.
- setCredentialRequiredToDecrypt(false);
- }
- updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
- }
-
- /**
* Store the hash of the *current* password in the password history list, if device policy
* enforces password history requirement.
*/
@@ -1927,8 +1910,8 @@
}
}
- protected boolean isCredentialSharedWithParent(int userId) {
- return getUserManagerFromCache(userId).isCredentialSharedWithParent();
+ protected boolean isCredentialSharableWithParent(int userId) {
+ return getUserManagerFromCache(userId).isCredentialSharableWithParent();
}
private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
@@ -1942,34 +1925,6 @@
}
}
- /** Update the encryption password if it is enabled **/
- @Override
- public void updateEncryptionPassword(final int type, final byte[] password) {
- if (!hasSecureLockScreen() && password != null && password.length != 0) {
- throw new UnsupportedOperationException(
- "This operation requires the lock screen feature.");
- }
- if (!isDeviceEncryptionEnabled()) {
- return;
- }
- final IBinder service = ServiceManager.getService("mount");
- if (service == null) {
- Slog.e(TAG, "Could not find the mount service to update the encryption password");
- return;
- }
-
- // TODO(b/120484642): This is a location where we still use a String for vold
- String passwordString = password != null ? new String(password) : null;
- mHandler.post(() -> {
- IStorageManager storageManager = mInjector.getStorageManager();
- try {
- storageManager.changeEncryptionPassword(type, passwordString);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error changing encryption password", e);
- }
- });
- }
-
/** Register the given WeakEscrowTokenRemovedListener. */
@Override
public boolean registerWeakEscrowTokenRemovedListener(
@@ -2206,7 +2161,7 @@
final List<UserInfo> profiles = mUserManager.getProfiles(userId);
for (UserInfo pi : profiles) {
// Unlock profile which shares credential with parent with unified lock
- if (isCredentialSharedWithParent(pi.id)
+ if (isCredentialSharableWithParent(pi.id)
&& !getSeparateProfileChallengeEnabledInternal(pi.id)
&& mStorage.hasChildProfileLock(pi.id)) {
try {
@@ -2600,7 +2555,7 @@
mManagedProfilePasswordCache.removePassword(userId);
gateKeeperClearSecureUserId(userId);
- if (unknownUser || isCredentialSharedWithParent(userId)) {
+ if (unknownUser || isCredentialSharableWithParent(userId)) {
removeKeystoreProfileKey(userId);
}
// Clean up storage last, this is to ensure that cleanupDataForReusedUserIdIfNecessary()
@@ -3320,7 +3275,7 @@
* Returns a fixed pseudorandom byte string derived from the user's synthetic password.
* This is used to salt the password history hash to protect the hash against offline
* bruteforcing, since rederiving this value requires a successful authentication.
- * If user is a profile with {@link UserManager#isCredentialSharedWithParent()} true and with
+ * If user is a profile with {@link UserManager#isCredentialSharableWithParent()} true and with
* unified challenge, currentCredential is ignored.
*/
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 265ad7d..1885b55 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -630,6 +630,7 @@
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
final boolean mEnableAppSettingMigration;
+ private boolean mForceUserSetOnUpgrade;
private MetricsLogger mMetricsLogger;
private TriPredicate<String, Integer, String> mAllowedManagedServicePackages;
@@ -2294,6 +2295,7 @@
mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource(
com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos));
+
mStatsManager = statsManager;
mToastRateLimiter = toastRateLimiter;
@@ -2386,6 +2388,9 @@
WorkerHandler handler = new WorkerHandler(Looper.myLooper());
+ mForceUserSetOnUpgrade = getContext().getResources().getBoolean(
+ R.bool.config_notificationForceUserSetOnUpgrade);
+
init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),
AppGlobals.getPackageManager(), getContext().getPackageManager(),
getLocalService(LightsManager.class),
@@ -2414,7 +2419,8 @@
LocalServices.getService(ActivityManagerInternal.class),
createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
- AppGlobals.getPermissionManager(), mEnableAppSettingMigration),
+ AppGlobals.getPermissionManager(), mEnableAppSettingMigration,
+ mForceUserSetOnUpgrade),
LocalServices.getService(UsageStatsManagerInternal.class));
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -6069,6 +6075,7 @@
pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
pw.println(" hideSilentStatusBar="
+ mPreferencesHelper.shouldHideSilentStatusIcons());
+ pw.println(" mForceUserSetOnUpgrade=" + mForceUserSetOnUpgrade);
}
pw.println(" mArchive=" + mArchive.toString());
mArchive.dumpImpl(pw, filter);
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index e551f10..b4230c1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -57,13 +57,16 @@
private final IPermissionManager mPermManager;
// TODO (b/194833441): Remove when the migration is enabled
private final boolean mMigrationEnabled;
+ private final boolean mForceUserSetOnUpgrade;
public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
- IPermissionManager permManager, boolean migrationEnabled) {
+ IPermissionManager permManager, boolean migrationEnabled,
+ boolean forceUserSetOnUpgrade) {
mPmi = pmi;
mPackageManager = packageManager;
mPermManager = permManager;
mMigrationEnabled = migrationEnabled;
+ mForceUserSetOnUpgrade = forceUserSetOnUpgrade;
}
public boolean isMigrationEnabled() {
@@ -223,8 +226,9 @@
return;
}
if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) {
+ boolean userSet = mForceUserSetOnUpgrade ? true : pkgPerm.userModifiedSettings;
setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
- pkgPerm.userSet, !pkgPerm.userSet);
+ userSet, !userSet);
}
}
@@ -305,13 +309,13 @@
public final String packageName;
public final @UserIdInt int userId;
public final boolean granted;
- public final boolean userSet;
+ public final boolean userModifiedSettings;
public PackagePermission(String pkg, int userId, boolean granted, boolean userSet) {
this.packageName = pkg;
this.userId = userId;
this.granted = granted;
- this.userSet = userSet;
+ this.userModifiedSettings = userSet;
}
@Override
@@ -319,13 +323,14 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PackagePermission that = (PackagePermission) o;
- return userId == that.userId && granted == that.granted && userSet == that.userSet
+ return userId == that.userId && granted == that.granted && userModifiedSettings
+ == that.userModifiedSettings
&& Objects.equals(packageName, that.packageName);
}
@Override
public int hashCode() {
- return Objects.hash(packageName, userId, granted, userSet);
+ return Objects.hash(packageName, userId, granted, userModifiedSettings);
}
@Override
@@ -334,7 +339,7 @@
"packageName='" + packageName + '\'' +
", userId=" + userId +
", granted=" + granted +
- ", userSet=" + userSet +
+ ", userSet=" + userModifiedSettings +
'}';
}
}
diff --git a/services/core/java/com/android/server/pm/AppIdSettingMap.java b/services/core/java/com/android/server/pm/AppIdSettingMap.java
new file mode 100644
index 0000000..bbef237
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppIdSettingMap.java
@@ -0,0 +1,62 @@
+/*
+ * 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.pm;
+
+import android.os.Process;
+
+import com.android.server.utils.WatchedSparseArray;
+
+/**
+ * A wrapper over {@link WatchedSparseArray} that tracks the current maximum App ID.
+ */
+public class AppIdSettingMap extends WatchedSparseArray<SettingBase> {
+ private int mCurrentMaxAppId;
+
+ @Override
+ public void put(int key, SettingBase value) {
+ if (key > mCurrentMaxAppId) {
+ mCurrentMaxAppId = key;
+ }
+ super.put(key, value);
+ }
+
+ @Override
+ public AppIdSettingMap snapshot() {
+ AppIdSettingMap l = new AppIdSettingMap();
+ snapshot(l, this);
+ return l;
+ }
+
+ /**
+ * @return the maximum of all the App IDs that have been added to the map. 0 if map is empty.
+ */
+ public int getCurrentMaxAppId() {
+ return mCurrentMaxAppId;
+ }
+
+ /**
+ * @return the next available App ID that has not been added to the map
+ */
+ public int getNextAvailableAppId() {
+ if (mCurrentMaxAppId == 0) {
+ // No app id has been added yet
+ return Process.FIRST_APPLICATION_UID;
+ } else {
+ return mCurrentMaxAppId + 1;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 6dbe9b6..8d77624 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -30,6 +30,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_CHECK_MAX_SDK_VERSION;
import android.annotation.Nullable;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -313,10 +314,14 @@
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
- final int parseFlags, int scanFlags,
+ int parseFlags, int scanFlags,
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
+ if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+ // when scanning apk in apexes, we want to check the maxSdkVersion
+ parseFlags |= PARSE_CHECK_MAX_SDK_VERSION;
+ }
mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
scanFlags, currentTime, packageParser, executorService);
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 35a7eaf..bcda046 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -126,6 +126,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.ExceptionUtils;
import android.util.IntArray;
import android.util.MathUtils;
@@ -2861,7 +2862,9 @@
inheritFileLocked(mResolvedBaseFile);
// Collect the requiredSplitTypes from base
CollectionUtils.addAll(requiredSplitTypes, existing.getBaseRequiredSplitTypes());
- } else {
+ } else if ((params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
+ EventLog.writeEvent(0x534e4554, "219044664");
+
// Installing base.apk. Make sure the app is restarted.
params.setDontKillApp(false);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 8d3fbf7..2a1a990 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -850,6 +850,8 @@
ret.recommendedInstallLocation = recommendedInstallLocation;
ret.multiArch = pkg.isMultiArch();
ret.debuggable = pkg.isDebuggable();
+ ret.isSdkLibrary = pkg.isIsSdkLibrary();
+
return ret;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index b92f51b..62e9d37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -131,6 +131,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -144,6 +145,10 @@
private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
private static final int DEFAULT_STAGED_READY_TIMEOUT_MS = 60 * 1000;
private static final String TAG = "PackageManagerShellCommand";
+ private static final Set<String> UNSUPPORTED_INSTALL_CMD_OPTS = Set.of(
+ "--multi-package"
+ );
+ private static final Set<String> UNSUPPORTED_SESSION_CREATE_OPTS = Collections.emptySet();
final IPackageManager mInterface;
final LegacyPermissionManagerInternal mLegacyPermissionManager;
@@ -1330,7 +1335,7 @@
}
private int runStreamingInstall() throws RemoteException {
- final InstallParams params = makeInstallParams();
+ final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
if (params.sessionParams.dataLoaderParams == null) {
params.sessionParams.setDataLoaderParams(
PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this));
@@ -1339,7 +1344,7 @@
}
private int runIncrementalInstall() throws RemoteException {
- final InstallParams params = makeInstallParams();
+ final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
if (params.sessionParams.dataLoaderParams == null) {
params.sessionParams.setDataLoaderParams(
PackageManagerShellCommandDataLoader.getIncrementalDataLoaderParams(this));
@@ -1348,7 +1353,7 @@
}
private int runInstall() throws RemoteException {
- return doRunInstall(makeInstallParams());
+ return doRunInstall(makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS));
}
private int doRunInstall(final InstallParams params) throws RemoteException {
@@ -1500,7 +1505,7 @@
private int runInstallCreate() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
- final InstallParams installParams = makeInstallParams();
+ final InstallParams installParams = makeInstallParams(UNSUPPORTED_SESSION_CREATE_OPTS);
final int sessionId = doCreateSession(installParams.sessionParams,
installParams.installerPackageName, installParams.userId);
@@ -2896,7 +2901,7 @@
long stagedReadyTimeoutMs = DEFAULT_STAGED_READY_TIMEOUT_MS;
}
- private InstallParams makeInstallParams() {
+ private InstallParams makeInstallParams(Set<String> unsupportedOptions) {
final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
final InstallParams params = new InstallParams();
@@ -2910,6 +2915,9 @@
boolean replaceExisting = true;
boolean forceNonStaged = false;
while ((opt = getNextOption()) != null) {
+ if (unsupportedOptions.contains(opt)) {
+ throw new IllegalArgumentException("Unsupported option " + opt);
+ }
switch (opt) {
case "-r": // ignore
break;
@@ -3817,7 +3825,7 @@
pw.println(" [--user USER_ID] INTENT");
pw.println(" Prints all broadcast receivers that can handle the given INTENT.");
pw.println("");
- pw.println(" install [-rtfdgw] [-i PACKAGE] [--user USER_ID|all|current]");
+ pw.println(" install [-rtfdg] [-i PACKAGE] [--user USER_ID|all|current]");
pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]");
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 021c3db..6ccaae1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -40,7 +40,6 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ComponentInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
@@ -113,11 +112,9 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.PackageUserStateUtils;
import com.android.server.pm.pkg.SuspendParams;
import com.android.server.pm.pkg.component.ParsedComponent;
import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedProcess;
import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
@@ -476,9 +473,9 @@
@Watched
final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>();
@Watched
- private final WatchedArrayList<SettingBase> mAppIds;
+ private final AppIdSettingMap mAppIds;
@Watched
- private final WatchedSparseArray<SettingBase> mOtherAppIds;
+ private final AppIdSettingMap mOtherAppIds;
// For reading/writing settings file.
@Watched
@@ -594,8 +591,8 @@
mLock = new PackageManagerTracedLock();
mPackages.putAll(pkgSettings);
- mAppIds = new WatchedArrayList<>();
- mOtherAppIds = new WatchedSparseArray<>();
+ mAppIds = new AppIdSettingMap();
+ mOtherAppIds = new AppIdSettingMap();
mSystemDir = null;
mPermissions = null;
mRuntimePermissionsPersistence = null;
@@ -631,8 +628,8 @@
mKeySetManagerService = new KeySetManagerService(mPackages);
mLock = lock;
- mAppIds = new WatchedArrayList<>();
- mOtherAppIds = new WatchedSparseArray<>();
+ mAppIds = new AppIdSettingMap();
+ mOtherAppIds = new AppIdSettingMap();
mPermissions = new LegacyPermissionSettings(lock);
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(
runtimePermissionsPersistence, new Consumer<Integer>() {
@@ -1278,7 +1275,8 @@
// Utility method that adds a PackageSetting to mPackages and
// completes updating the shared user attributes and any restored
// app link verification state
- private void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) {
mPackages.put(p.getPackageName(), p);
if (sharedUser != null) {
SharedUserSetting existingSharedUserSetting = getSharedUserSettingLPr(p);
@@ -1301,7 +1299,7 @@
p.setAppId(sharedUser.mAppId);
}
- // If the we know about this user id, we have to update it as it
+ // If we know about this user id, we have to update it as it
// has to point to the same PackageSetting instance as the package.
Object userIdPs = getSettingLPr(p.getAppId());
if (sharedUser == null) {
@@ -1366,20 +1364,13 @@
}
if (appId >= Process.FIRST_APPLICATION_UID) {
- int size = mAppIds.size();
- final int index = appId - Process.FIRST_APPLICATION_UID;
- // fill the array until our index becomes valid
- while (index >= size) {
- mAppIds.add(null);
- size++;
- }
- if (mAppIds.get(index) != null) {
+ if (mAppIds.get(appId) != null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Adding duplicate app id: " + appId
+ " name=" + name);
return false;
}
- mAppIds.set(index, obj);
+ mAppIds.put(appId, obj);
} else {
if (mOtherAppIds.get(appId) != null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -1395,9 +1386,7 @@
/** Gets the setting associated with the provided App ID */
public SettingBase getSettingLPr(int appId) {
if (appId >= Process.FIRST_APPLICATION_UID) {
- final int size = mAppIds.size();
- final int index = appId - Process.FIRST_APPLICATION_UID;
- return index < size ? mAppIds.get(index) : null;
+ return mAppIds.get(appId);
} else {
return mOtherAppIds.get(appId);
}
@@ -1406,9 +1395,7 @@
/** Unregisters the provided app ID. */
void removeAppIdLPw(int appId) {
if (appId >= Process.FIRST_APPLICATION_UID) {
- final int size = mAppIds.size();
- final int index = appId - Process.FIRST_APPLICATION_UID;
- if (index < size) mAppIds.set(index, null);
+ mAppIds.remove(appId);
} else {
mOtherAppIds.remove(appId);
}
@@ -1417,9 +1404,14 @@
private void replaceAppIdLPw(int appId, SettingBase obj) {
if (appId >= Process.FIRST_APPLICATION_UID) {
- final int size = mAppIds.size();
- final int index = appId - Process.FIRST_APPLICATION_UID;
- if (index < size) mAppIds.set(index, obj);
+ if (appId <= mAppIds.getCurrentMaxAppId()) {
+ mAppIds.put(appId, obj);
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: calling replaceAppIdLpw to"
+ + " replace SettingBase at appId=" + appId
+ + " but nothing is replaced.");
+ }
} else {
mOtherAppIds.put(appId, obj);
}
@@ -4304,22 +4296,21 @@
/** Returns a new AppID or -1 if we could not find an available AppID to assign */
private int acquireAndRegisterNewAppIdLPw(SettingBase obj) {
- // Let's be stupidly inefficient for now...
- final int size = mAppIds.size();
- for (int i = mFirstAvailableUid - Process.FIRST_APPLICATION_UID; i < size; i++) {
- if (mAppIds.get(i) == null) {
- mAppIds.set(i, obj);
- return Process.FIRST_APPLICATION_UID + i;
+ final int nextAvailableAppId = mAppIds.getNextAvailableAppId();
+ for (int uid = mFirstAvailableUid; uid < nextAvailableAppId; uid++) {
+ if (mAppIds.get(uid) == null) {
+ mAppIds.put(uid, obj);
+ return uid;
}
}
// None left?
- if (size > (Process.LAST_APPLICATION_UID - Process.FIRST_APPLICATION_UID)) {
+ if (nextAvailableAppId > Process.LAST_APPLICATION_UID) {
return -1;
}
- mAppIds.add(obj);
- return Process.FIRST_APPLICATION_UID + size;
+ mAppIds.put(nextAvailableAppId, obj);
+ return nextAvailableAppId;
}
public VerifierDeviceIdentity getVerifierDeviceIdentityLPw(@NonNull Computer computer) {
@@ -4354,33 +4345,6 @@
return getDisabledSystemPkgLPr(enabledPackageSetting.getPackageName());
}
- boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, long flags, int userId) {
- final PackageSetting ps = mPackages.get(componentInfo.packageName);
- if (ps == null) return false;
-
- final PackageUserStateInternal userState = ps.readUserState(userId);
- return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component,
- long flags, int userId) {
- final PackageSetting ps = mPackages.get(component.getPackageName());
- if (ps == null) return false;
-
- final PackageUserStateInternal userState = ps.readUserState(userId);
- return PackageUserStateUtils.isMatch(userState, pkg.isSystem(), pkg.isEnabled(), component,
- flags);
- }
-
- boolean isOrphaned(String packageName) {
- final PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- return pkg.getInstallSource().isOrphaned;
- }
-
int getApplicationEnabledSettingLPr(String packageName, int userId)
throws PackageManager.NameNotFoundException {
final PackageSetting pkg = mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a8d24fa..33476ed 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1593,13 +1593,13 @@
}
@Override
- public boolean isCredentialSharedWithParent(@UserIdInt int userId) {
+ public boolean isCredentialSharableWithParent(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
- "isCredentialSharedWithParent");
+ "isCredentialSharableWithParent");
synchronized (mUsersLock) {
UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
return userTypeDetails != null && userTypeDetails.isProfile()
- && userTypeDetails.isCredentialSharedWithParent();
+ && userTypeDetails.isCredentialSharableWithParent();
}
}
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 2f5e238..4aad1a7 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -161,7 +161,7 @@
*
* <p> Default value is false
*/
- private final boolean mIsCredentialSharedWithParent;
+ private final boolean mIsCredentialSharableWithParent;
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
@@ -174,7 +174,7 @@
@Nullable Bundle defaultSecureSettings,
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean isMediaSharedWithParent,
- boolean isCredentialSharedWithParent) {
+ boolean isCredentialSharableWithParent) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -194,7 +194,7 @@
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
this.mIsMediaSharedWithParent = isMediaSharedWithParent;
- this.mIsCredentialSharedWithParent = isCredentialSharedWithParent;
+ this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
}
/**
@@ -323,8 +323,8 @@
* Returns true if the user has shared encryption credential with parent user or
* false otherwise.
*/
- public boolean isCredentialSharedWithParent() {
- return mIsCredentialSharedWithParent;
+ public boolean isCredentialSharableWithParent() {
+ return mIsCredentialSharableWithParent;
}
/** Returns a {@link Bundle} representing the default user restrictions. */
@@ -419,7 +419,7 @@
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
private boolean mIsMediaSharedWithParent = false;
- private boolean mIsCredentialSharedWithParent = false;
+ private boolean mIsCredentialSharableWithParent = false;
public Builder setName(String name) {
mName = name;
@@ -521,10 +521,10 @@
/**
* Sets shared media property for the user.
- * @param isCredentialSharedWithParent the value to be set, true or false
+ * @param isCredentialSharableWithParent the value to be set, true or false
*/
- public Builder setIsCredentialSharedWithParent(boolean isCredentialSharedWithParent) {
- mIsCredentialSharedWithParent = isCredentialSharedWithParent;
+ public Builder setIsCredentialSharableWithParent(boolean isCredentialSharableWithParent) {
+ mIsCredentialSharableWithParent = isCredentialSharableWithParent;
return this;
}
@@ -571,7 +571,7 @@
mDefaultSecureSettings,
mDefaultCrossProfileIntentFilters,
mIsMediaSharedWithParent,
- mIsCredentialSharedWithParent);
+ mIsCredentialSharableWithParent);
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 1e3b67c..cb18c6d 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -122,7 +122,7 @@
.setLabel(0)
.setDefaultRestrictions(null)
.setIsMediaSharedWithParent(true)
- .setIsCredentialSharedWithParent(true);
+ .setIsCredentialSharableWithParent(true);
}
/**
@@ -154,7 +154,7 @@
.setDefaultRestrictions(getDefaultManagedProfileRestrictions())
.setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
.setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
- .setIsCredentialSharedWithParent(true);
+ .setIsCredentialSharableWithParent(true);
}
/**
diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java
index bc93611..7423bf6 100644
--- a/services/core/java/com/android/server/pm/VerificationParams.java
+++ b/services/core/java/com/android/server/pm/VerificationParams.java
@@ -72,6 +72,7 @@
import android.util.Slog;
import com.android.server.DeviceIdleInternal;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import java.io.File;
import java.util.ArrayList;
@@ -441,9 +442,22 @@
final long verificationTimeout = VerificationUtils.getVerificationTimeout(mPm.mContext,
streaming);
- final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
+ List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
receivers.getList(), verificationState);
+ // Add broadcastReceiver Component to verify Sdk before run in Sdk sandbox.
+ if (pkgLite.isSdkLibrary) {
+ if (sufficientVerifiers == null) {
+ sufficientVerifiers = new ArrayList<>();
+ }
+ ComponentName sdkSandboxComponentName = new ComponentName("android",
+ SdkSandboxManagerLocal.VERIFIER_RECEIVER);
+ sufficientVerifiers.add(sdkSandboxComponentName);
+
+ // Add uid of system_server the same uid for SdkSandboxManagerService
+ verificationState.addSufficientVerifier(Process.myUid());
+ }
+
DeviceIdleInternal idleController =
mPm.mInjector.getLocalService(DeviceIdleInternal.class);
final BroadcastOptions options = BroadcastOptions.makeBasic();
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index cbba346..40f859c 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -302,6 +302,8 @@
ParsingPackage setMinSdkVersion(int minSdkVersion);
+ ParsingPackage setMaxSdkVersion(int maxSdkVersion);
+
ParsingPackage setNetworkSecurityConfigRes(int networkSecurityConfigRes);
ParsingPackage setNonLocalizedLabel(CharSequence nonLocalizedLabel);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 1484df8..67d9aec 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -383,6 +383,7 @@
@Nullable
private SparseIntArray minExtensionVersions;
private int minSdkVersion = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
+ private int maxSdkVersion = ParsingUtils.DEFAULT_MAX_SDK_VERSION;
private int networkSecurityConfigRes;
@Nullable
private CharSequence nonLocalizedLabel;
@@ -1306,6 +1307,7 @@
dest.writeFloat(this.maxAspectRatio);
dest.writeFloat(this.minAspectRatio);
dest.writeInt(this.minSdkVersion);
+ dest.writeInt(this.maxSdkVersion);
dest.writeInt(this.networkSecurityConfigRes);
dest.writeCharSequence(this.nonLocalizedLabel);
dest.writeString(this.permission);
@@ -1454,6 +1456,7 @@
this.maxAspectRatio = in.readFloat();
this.minAspectRatio = in.readFloat();
this.minSdkVersion = in.readInt();
+ this.maxSdkVersion = in.readInt();
this.networkSecurityConfigRes = in.readInt();
this.nonLocalizedLabel = in.readCharSequence();
this.permission = in.readString();
@@ -2068,6 +2071,11 @@
}
@Override
+ public int getMaxSdkVersion() {
+ return maxSdkVersion;
+ }
+
+ @Override
public int getNetworkSecurityConfigRes() {
return networkSecurityConfigRes;
}
@@ -2592,6 +2600,12 @@
}
@Override
+ public ParsingPackageImpl setMaxSdkVersion(int value) {
+ maxSdkVersion = value;
+ return this;
+ }
+
+ @Override
public ParsingPackageImpl setNetworkSecurityConfigRes(int value) {
networkSecurityConfigRes = value;
return this;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 112b9e0..b323948 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -209,6 +209,8 @@
public static final int SDK_VERSION = Build.VERSION.SDK_INT;
public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+ public static final String[] PREVIOUS_CODENAMES =
+ Build.VERSION.KNOWN_CODENAMES.toArray(new String[]{});
public static boolean sCompatibilityModeEnabled = true;
public static boolean sUseRoundIcon = false;
@@ -236,6 +238,7 @@
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
+ public static final int PARSE_CHECK_MAX_SDK_VERSION = 1 << 9;
public static final int PARSE_CHATTY = 1 << 31;
@@ -1002,7 +1005,7 @@
case TAG_FEATURE_GROUP:
return parseFeatureGroup(input, pkg, res, parser);
case TAG_USES_SDK:
- return parseUsesSdk(input, pkg, res, parser);
+ return parseUsesSdk(input, pkg, res, parser, flags);
case TAG_SUPPORT_SCREENS:
return parseSupportScreens(input, pkg, res, parser);
case TAG_PROTECTED_BROADCAST:
@@ -1514,15 +1517,17 @@
}
private static ParseResult<ParsingPackage> parseUsesSdk(ParseInput input,
- ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws IOException, XmlPullParserException {
if (SDK_VERSION > 0) {
+ final boolean checkMaxSdkVersion = (flags & PARSE_CHECK_MAX_SDK_VERSION) != 0;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
try {
int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
String minCode = null;
int targetVers = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
String targetCode = null;
+ int maxVers = Integer.MAX_VALUE;
TypedValue val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersion);
if (val != null) {
@@ -1550,6 +1555,14 @@
targetCode = minCode;
}
+ if (checkMaxSdkVersion) {
+ val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
+ if (val != null) {
+ // maxSdkVersion only supports integer
+ maxVers = val.data;
+ }
+ }
+
ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils
.computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input);
if (targetSdkVersionResult.isError()) {
@@ -1574,6 +1587,15 @@
pkg.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
+ if (checkMaxSdkVersion) {
+ ParseResult<Integer> maxSdkVersionResult = FrameworkParsingPackageUtils
+ .computeMaxSdkVersion(maxVers, SDK_VERSION, input);
+ if (maxSdkVersionResult.isError()) {
+ return input.error(maxSdkVersionResult);
+ }
+ int maxSdkVersion = maxSdkVersionResult.getResult();
+ pkg.setMaxSdkVersion(maxSdkVersion);
+ }
int type;
final int innerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index cb474df..0751285 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -50,6 +50,7 @@
public static final String ANDROID_RES_NAMESPACE = "http://schemas.android.com/apk/res/android";
public static final int DEFAULT_MIN_SDK_VERSION = 1;
+ public static final int DEFAULT_MAX_SDK_VERSION = Integer.MAX_VALUE;
public static final int DEFAULT_TARGET_SDK_VERSION = 0;
public static final int NOT_SET = -1;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
index 3205b76..99bcdb96 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
@@ -250,6 +250,11 @@
int getMinSdkVersion();
/**
+ * @see R.styleable#AndroidManifestUsesSdk_maxSdkVersion
+ */
+ int getMaxSdkVersion();
+
+ /**
* @see ApplicationInfo#getNativeHeapZeroInitialized()
* @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
*/
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index ee0e5ba..e3dcfd0 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -247,6 +247,8 @@
if (autoGrantPermissions != null && callingPkg != null) {
// Need to own the Uri to call in with permissions to grant.
enforceOwner(callingPkg, uri, userId);
+ // b/208232850: Needs to verify caller before granting slice access
+ verifyCaller(callingPkg);
for (String perm : autoGrantPermissions) {
if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
int providerUser = ContentProvider.getUserIdFromUri(uri, userId);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 59b9daf..8087738 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -36,6 +36,7 @@
import android.app.StatusBarManager;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Context;
@@ -135,6 +136,17 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
private static final long LOCK_DOWN_COLLAPSE_STATUS_BAR = 173031413L;
+ /**
+ * In apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher, calling
+ * {@link android.service.quicksettings.TileService#requestListeningState} will check that the
+ * calling package (uid) and the package of the target {@link android.content.ComponentName}
+ * match. It'll also make sure that the context used can take actions on behalf of the current
+ * user.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
+ static final long REQUEST_LISTENING_MUST_MATCH_PACKAGE = 172251878L;
+
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -1652,6 +1664,7 @@
@Override
public void hideCurrentInputMethodForBubbles() {
+ enforceStatusBarService();
final long token = Binder.clearCallingIdentity();
try {
InputMethodManagerInternal.get().hideCurrentInputMethod(
@@ -1776,6 +1789,42 @@
}
@Override
+ public void requestTileServiceListeningState(
+ @NonNull ComponentName componentName,
+ int userId
+ ) {
+ int callingUid = Binder.getCallingUid();
+ String packageName = componentName.getPackageName();
+
+ boolean mustPerformChecks = CompatChanges.isChangeEnabled(
+ REQUEST_LISTENING_MUST_MATCH_PACKAGE, callingUid);
+
+ if (mustPerformChecks) {
+ // Check calling user can act on behalf of current user
+ userId = mActivityManagerInternal.handleIncomingUser(Binder.getCallingPid(), callingUid,
+ userId, false, ActivityManagerInternal.ALLOW_NON_FULL,
+ "requestTileServiceListeningState", packageName);
+
+ // Check calling uid matches package
+ checkCallingUidPackage(packageName, callingUid, userId);
+
+ int currentUser = mActivityManagerInternal.getCurrentUserId();
+
+ // Check current user
+ if (userId != currentUser) {
+ throw new IllegalArgumentException("User " + userId + " is not the current user.");
+ }
+ }
+ if (mBar != null) {
+ try {
+ mBar.requestTileServiceListeningState(componentName);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "requestTileServiceListeningState", e);
+ }
+ }
+ }
+
+ @Override
public void requestAddTile(
@NonNull ComponentName componentName,
@NonNull CharSequence label,
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index bd4b8d1..cc1d0e2 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -715,7 +715,7 @@
(disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0;
List<ComponentName> enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id);
- if (enabledAgents == null) {
+ if (enabledAgents.isEmpty()) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ ": no agents enabled by user");
continue;
@@ -1080,9 +1080,7 @@
}
List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
- if (previouslyEnabledAgents != null) {
- discoveredAgents.addAll(previouslyEnabledAgents);
- }
+ discoveredAgents.addAll(previouslyEnabledAgents);
utils.setEnabledTrustAgents(discoveredAgents, userId);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index a4a200d..543e44c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -757,12 +757,12 @@
}
@Override
- public void setPreferDockBigOverlays(IBinder token, boolean preferDockBigOverlays) {
+ public void setShouldDockBigOverlays(IBinder token, boolean shouldDockBigOverlays) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- r.setPreferDockBigOverlays(preferDockBigOverlays);
+ r.setShouldDockBigOverlays(shouldDockBigOverlays);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1c93abd..677babe 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -534,7 +534,7 @@
// activity can enter picture in picture while pausing (only when switching to another task)
PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build();
// The PiP params used when deferring the entering of picture-in-picture.
- boolean preferDockBigOverlays;
+ boolean shouldDockBigOverlays;
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last launch of this activity
ComponentName requestedVrComponent; // the requested component for handling VR mode.
@@ -2042,7 +2042,7 @@
mLetterboxUiController = new LetterboxUiController(mWmService, this);
mCameraCompatControlEnabled = mWmService.mContext.getResources()
.getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
- preferDockBigOverlays = mWmService.mContext.getResources()
+ shouldDockBigOverlays = mWmService.mContext.getResources()
.getBoolean(R.bool.config_dockBigOverlayWindows);
if (_createTime > 0) {
@@ -6767,7 +6767,8 @@
}
// Choose the default behavior for Launcher and SystemUI when the SplashScreen style is
// not specified in the ActivityOptions.
- if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) {
+ if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME
+ || launchedFromUid == Process.SHELL_UID) {
return false;
} else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
return true;
@@ -6787,7 +6788,8 @@
// solid color splash screen.
// Need to check sourceRecord before in case this activity is launched from service.
return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM
- || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME);
+ || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME
+ || launchedFromUid == Process.SHELL_UID);
}
private int getSplashscreenTheme(ActivityOptions options) {
@@ -9573,9 +9575,9 @@
getTask().getRootTask().onPictureInPictureParamsChanged();
}
- void setPreferDockBigOverlays(boolean preferDockBigOverlays) {
- this.preferDockBigOverlays = preferDockBigOverlays;
- getTask().getRootTask().onPreferDockBigOverlaysChanged();
+ void setShouldDockBigOverlays(boolean shouldDockBigOverlays) {
+ this.shouldDockBigOverlays = shouldDockBigOverlays;
+ getTask().getRootTask().onShouldDockBigOverlaysChanged();
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ac121a1..7d2dfa0 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2023,22 +2023,12 @@
private boolean canEmbedActivity(@NonNull TaskFragment taskFragment,
@NonNull ActivityRecord starting, boolean newTask, Task targetTask) {
final Task hostTask = taskFragment.getTask();
- if (hostTask == null) {
+ // Not allowed embedding a separate task or without host task.
+ if (hostTask == null || newTask || targetTask != hostTask) {
return false;
}
- // Allowing the embedding if the task is owned by system.
- final int hostUid = hostTask.effectiveUid;
- if (UserHandle.getAppId(hostUid) == Process.SYSTEM_UID) {
- return true;
- }
-
- if (!taskFragment.isAllowedToEmbedActivity(starting)) {
- return false;
- }
-
- // Not allowed embedding task.
- return !newTask && (targetTask == null || targetTask == hostTask);
+ return taskFragment.isAllowedToEmbedActivity(starting);
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6d8b3b1d..b5312c4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -205,8 +205,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageManager;
import android.provider.Settings;
import android.service.dreams.DreamActivity;
import android.service.dreams.DreamManagerInternal;
@@ -4300,11 +4298,6 @@
SystemProperties.set("persist.sys.locale",
locales.get(bestLocaleIndex).toLanguageTag());
LocaleList.setDefault(locales, bestLocaleIndex);
-
- final Message m = PooledLambda.obtainMessage(
- ActivityTaskManagerService::sendLocaleToMountDaemonMsg, this,
- locales.get(bestLocaleIndex));
- mH.sendMessage(m);
}
mTempConfig.seq = increaseConfigurationSeqLocked();
@@ -4458,17 +4451,6 @@
Settings.System.putConfigurationForUser(resolver, config, userId);
}
- private void sendLocaleToMountDaemonMsg(Locale l) {
- try {
- IBinder service = ServiceManager.getService("mount");
- IStorageManager storageManager = IStorageManager.Stub.asInterface(service);
- Log.d(TAG, "Storing locale " + l.toLanguageTag() + " for decryption UI");
- storageManager.setField(StorageManager.SYSTEM_LOCALE_KEY, l.toLanguageTag());
- } catch (RemoteException e) {
- Log.e(TAG, "Error storing locale for decryption UI", e);
- }
- }
-
private void expireStartAsCallerTokenMsg(IBinder permissionToken) {
mStartActivitySources.remove(permissionToken);
mExpiredStartAsCallerTokens.add(permissionToken);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index c55af9b..7bf150b 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -443,6 +443,13 @@
|| !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
}
+ /**
+ * @return Whether the dream activity is on top of default display.
+ */
+ boolean isShowingDream() {
+ return getDisplayState(DEFAULT_DISPLAY).mShowingDream;
+ }
+
private void dismissMultiWindowModeForTaskIfNeeded(int displayId,
@Nullable Task currentTaskControllingOcclusion) {
// TODO(b/113840485): Handle docked stack for individual display.
@@ -501,6 +508,7 @@
private boolean mKeyguardGoingAway;
private boolean mDismissalRequested;
private boolean mOccluded;
+ private boolean mShowingDream;
private ActivityRecord mTopOccludesActivity;
private ActivityRecord mDismissingKeyguardActivity;
@@ -536,6 +544,7 @@
mRequestDismissKeyguard = false;
mOccluded = false;
+ mShowingDream = false;
mTopOccludesActivity = null;
mDismissingKeyguardActivity = null;
@@ -570,9 +579,9 @@
}
}
- final boolean dreaming = display.getDisplayPolicy().isShowingDreamLw() && (top != null
+ mShowingDream = display.getDisplayPolicy().isShowingDreamLw() && (top != null
&& top.getActivityType() == ACTIVITY_TYPE_DREAM);
- mOccluded = dreaming || occludedByActivity;
+ mOccluded = mShowingDream || occludedByActivity;
mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
&& !mOccluded
&& mDismissingKeyguardActivity != null
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2355333..94fc51d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3376,11 +3376,17 @@
return new ArrayList<>();
}
} else {
+ final RecentTasks recentTasks = mWindowManager.mAtmService.getRecentTasks();
+ final int recentsComponentUid = recentTasks != null
+ ? recentTasks.getRecentsComponentUid()
+ : -1;
final ArrayList<ActivityRecord> activities = new ArrayList<>();
- forAllRootTasks(rootTask -> {
- if (!dumpVisibleRootTasksOnly || rootTask.shouldBeVisible(null)) {
- activities.addAll(rootTask.getDumpActivitiesLocked(name, userId));
+ forAllLeafTasks(task -> {
+ final boolean isRecents = (task.effectiveUid == recentsComponentUid);
+ if (!dumpVisibleRootTasksOnly || task.shouldBeVisible(null) || isRecents) {
+ activities.addAll(task.getDumpActivitiesLocked(name, userId));
}
+ return false;
});
return activities;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b3ae602..cd7ebe3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1523,7 +1523,7 @@
mTaskSupervisor.removeTask(this, false /* killProcess */,
!REMOVE_FROM_RECENTS, reason);
}
- } else if (!mReuseTask && !mCreatedByOrganizer) {
+ } else if (!mReuseTask && shouldRemoveSelfOnLastChildRemoval()) {
// Remove entire task if it doesn't have any activity left and it isn't marked for reuse
// or created by task organizer.
if (!isRootTask()) {
@@ -2036,8 +2036,10 @@
Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds();
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
- // Use empty bounds to indicate "fill parent".
- outOverrideBounds.setEmpty();
+ if (!mCreatedByOrganizer) {
+ // Use empty bounds to indicate "fill parent".
+ outOverrideBounds.setEmpty();
+ }
// The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
// the parent or display is smaller than the size, the content may be cropped.
return;
@@ -3401,7 +3403,7 @@
info.positionInParent = getRelativePosition();
info.pictureInPictureParams = getPictureInPictureParams(top);
- info.preferDockBigOverlays = getPreferDockBigOverlays();
+ info.shouldDockBigOverlays = shouldDockBigOverlays();
if (info.pictureInPictureParams != null
&& info.pictureInPictureParams.isLaunchIntoPip()
&& top.getTopMostActivity().getLastParentBeforePip() != null) {
@@ -3454,9 +3456,9 @@
? null : new PictureInPictureParams(topMostActivity.pictureInPictureArgs);
}
- private boolean getPreferDockBigOverlays() {
+ private boolean shouldDockBigOverlays() {
final ActivityRecord topMostActivity = getTopMostActivity();
- return topMostActivity != null && topMostActivity.preferDockBigOverlays;
+ return topMostActivity != null && topMostActivity.shouldDockBigOverlays;
}
Rect getDisplayCutoutInsets() {
@@ -4352,7 +4354,7 @@
}
}
- void onPreferDockBigOverlaysChanged() {
+ void onShouldDockBigOverlaysChanged() {
dispatchTaskInfoChangedIfNeeded(true /* force */);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3411104..597d29f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -242,7 +242,7 @@
/** Client assigned unique token for this TaskFragment if this is created by an organizer. */
@Nullable
- private IBinder mFragmentToken;
+ private final IBinder mFragmentToken;
/**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
@@ -2304,6 +2304,10 @@
mMinHeight = minHeight;
}
+ boolean shouldRemoveSelfOnLastChildRemoval() {
+ return !mCreatedByOrganizer || mIsRemovalRequested;
+ }
+
@Override
void removeChild(WindowContainer child) {
removeChild(child, true /* removeSelfIfPossible */);
@@ -2319,7 +2323,7 @@
mBackScreenshots.remove(r.mActivityComponent.flattenToString());
}
}
- if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) {
+ if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) {
removeImmediately("removeLastChild " + child);
}
}
@@ -2337,13 +2341,18 @@
return;
}
mIsRemovalRequested = true;
- forAllActivities(r -> {
- if (withTransition) {
+ // The task order may be changed by finishIfPossible() for adjusting focus if there are
+ // nested tasks, so add all activities into a list to avoid missed removals.
+ final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
+ forAllActivities((Consumer<ActivityRecord>) removingActivities::add);
+ for (int i = removingActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = removingActivities.get(i);
+ if (withTransition && r.isVisible()) {
r.finishIfPossible(reason, false /* oomAdj */);
} else {
r.destroyIfPossible(reason);
}
- });
+ }
}
void setDelayLastActivityRemoval(boolean delay) {
@@ -2383,10 +2392,18 @@
void removeImmediately() {
mIsRemovalRequested = false;
resetAdjacentTaskFragment();
+ cleanUp();
super.removeImmediately();
sendTaskFragmentVanished();
}
+ /** Called on remove to cleanup. */
+ private void cleanUp() {
+ if (mIsEmbedded) {
+ mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
+ }
+ }
+
@Override
Dimmer getDimmer() {
// If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 19f921d..bdec49e 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -140,7 +140,7 @@
mLastSentTaskFragmentInfos.put(tf, info);
tf.mTaskFragmentAppearedSent = true;
} catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onTaskFragmentAppeared callback", e);
+ Slog.d(TAG, "Exception sending onTaskFragmentAppeared callback", e);
}
onTaskFragmentParentInfoChanged(organizer, tf);
}
@@ -150,7 +150,7 @@
try {
organizer.onTaskFragmentVanished(tf.getTaskFragmentInfo());
} catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onTaskFragmentVanished callback", e);
+ Slog.d(TAG, "Exception sending onTaskFragmentVanished callback", e);
}
tf.mTaskFragmentAppearedSent = false;
mLastSentTaskFragmentInfos.remove(tf);
@@ -175,7 +175,7 @@
organizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo());
mLastSentTaskFragmentInfos.put(tf, info);
} catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
+ Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
}
}
@@ -198,7 +198,7 @@
organizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig);
mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
} catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
+ Slog.d(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
}
}
@@ -210,7 +210,7 @@
try {
organizer.onTaskFragmentError(errorCallbackToken, exceptionBundle);
} catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onTaskFragmentError callback", e);
+ Slog.d(TAG, "Exception sending onTaskFragmentError callback", e);
}
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4c23f39..7f32447 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -491,7 +491,8 @@
// Avoid commit visibility to false here, or else we will get a sudden
// "flash" / surface going invisible for a split second.
commitVisibility = false;
- } else {
+ } else if (ar.getDeferHidingClient()) {
+ // Legacy PIP-enter requires pause event with user-leaving.
mController.mAtm.mTaskSupervisor.mUserLeaving = true;
ar.getTaskFragment().startPausing(false /* uiSleeping */,
null /* resuming */, "finishTransition");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4e47659..6718235 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3233,8 +3233,8 @@
if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
throw new SecurityException("Requires CONTROL_KEYGUARD permission");
}
- if (mAtmService.isDreaming()) {
- mAtmService.mTaskSupervisor.wakeUp("dismissKeyguard");
+ if (mAtmService.mKeyguardController.isShowingDream()) {
+ mAtmService.mTaskSupervisor.wakeUp("leaveDream");
}
synchronized (mGlobalLock) {
mPolicy.dismissKeyguardLw(callback, message);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 1cf4c1b..5a2f28f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -566,9 +566,14 @@
try (ZipOutputStream out = new ZipOutputStream(getRawOutputStream())) {
ArrayList<Pair<String, ByteTransferPipe>> requestList = new ArrayList<>();
synchronized (mInternal.mGlobalLock) {
+ final RecentTasks recentTasks = mInternal.mAtmService.getRecentTasks();
+ final int recentsComponentUid = recentTasks != null
+ ? recentTasks.getRecentsComponentUid()
+ : -1;
// Request dump from all windows parallelly before writing to disk.
mInternal.mRoot.forAllWindows(w -> {
- if (w.isVisible()) {
+ final boolean isRecents = (w.getUid() == recentsComponentUid);
+ if (w.isVisible() || isRecents) {
ByteTransferPipe pipe = null;
try {
pipe = new ByteTransferPipe();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 81344ac..d862012 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1470,6 +1470,10 @@
return mLaunchTaskFragments.get(tfToken);
}
+ void cleanUpEmbeddedTaskFragment(TaskFragment taskFragment) {
+ mLaunchTaskFragments.remove(taskFragment.getFragmentToken());
+ }
+
static class CallerInfo {
final int mPid;
final int mUid;
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 1c6a3b5..0cd9494 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -106,7 +106,6 @@
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
- register_android_graphics_GraphicsStatsService(env);
register_android_hardware_display_DisplayViewport(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fa2850a..f7c66c5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -293,8 +293,6 @@
"com.android.server.wifi.p2p.WifiP2pService";
private static final String LOWPAN_SERVICE_CLASS =
"com.android.server.lowpan.LowpanService";
- private static final String ETHERNET_SERVICE_CLASS =
- "com.android.server.ethernet.EthernetService";
private static final String JOB_SCHEDULER_SERVICE_CLASS =
"com.android.server.job.JobSchedulerService";
private static final String LOCK_SETTINGS_SERVICE_CLASS =
@@ -423,6 +421,8 @@
private static final String SDK_SANDBOX_MANAGER_SERVICE_CLASS =
"com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
+ private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
+ "com.android.server.adservices.AdServicesManagerService$Lifecycle";
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
@@ -1993,13 +1993,6 @@
t.traceEnd();
}
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
- t.traceBegin("StartEthernet");
- mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
- t.traceEnd();
- }
-
t.traceBegin("StartPacProxyService");
try {
pacProxyService = new PacProxyService(context);
@@ -2608,6 +2601,11 @@
mSystemServiceManager.startService(SDK_SANDBOX_MANAGER_SERVICE_CLASS);
t.traceEnd();
+ // AdServicesManagerService (PP API service)
+ t.traceBegin("StartAdServicesManagerService");
+ mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
if (safeMode) {
mActivityManagerService.enterSafeMode();
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index c5f990d..66e840b 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -112,7 +112,7 @@
try {
mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
} catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage());
}
}
@@ -123,7 +123,7 @@
try {
return !mIProfcollect.get_supported_provider().isEmpty();
} catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage());
return false;
}
}
@@ -219,7 +219,8 @@
try {
sSelfService.mIProfcollect.process();
} catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "Failed to process profiles in background: "
+ + e.getMessage());
}
});
return true;
@@ -234,8 +235,11 @@
// Event observers
private void registerObservers() {
- registerAppLaunchObserver();
- registerOTAObserver();
+ BackgroundThread.get().getThreadHandler().post(
+ () -> {
+ registerAppLaunchObserver();
+ registerOTAObserver();
+ });
}
private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
@@ -264,7 +268,7 @@
try {
mIProfcollect.trace_once("applaunch");
} catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
});
}
@@ -348,7 +352,7 @@
.putExtra("filename", reportName);
context.sendBroadcast(intent);
} catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "Failed to upload report: " + e.getMessage());
}
});
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index 575e351..319a769 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -19,6 +19,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState;
@@ -26,19 +27,21 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
+import android.app.IProcessObserver;
import android.app.ITaskStackListener;
import android.content.ComponentName;
import android.content.Context;
@@ -135,6 +138,8 @@
private MockitoSession mMockingSession;
private GameServiceProviderInstance mGameServiceProviderInstance;
@Mock
+ private ActivityManagerInternal mMockActivityManagerInternal;
+ @Mock
private IActivityTaskManager mMockActivityTaskManager;
@Mock
private WindowManagerService mMockWindowManagerService;
@@ -151,6 +156,7 @@
private FakeGameSessionService mFakeGameSessionService;
private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
private ArrayList<ITaskStackListener> mTaskStackListeners;
+ private ArrayList<IProcessObserver> mProcessObservers;
private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners;
private ArrayList<RunningTaskInfo> mRunningTaskInfos;
@@ -185,6 +191,16 @@
return null;
}).when(mMockActivityTaskManager).unregisterTaskStackListener(any());
+ mProcessObservers = new ArrayList<>();
+ doAnswer(invocation -> {
+ mProcessObservers.add(invocation.getArgument(0));
+ return null;
+ }).when(mMockActivityManager).registerProcessObserver(any());
+ doAnswer(invocation -> {
+ mProcessObservers.remove(invocation.getArgument(0));
+ return null;
+ }).when(mMockActivityManager).unregisterProcessObserver(any());
+
mTaskSystemBarsListeners = new ArrayList<>();
doAnswer(invocation -> {
mTaskSystemBarsListeners.add(invocation.getArgument(0));
@@ -206,6 +222,7 @@
mMockContext,
mFakeGameClassifier,
mMockActivityManager,
+ mMockActivityManagerInternal,
mMockActivityTaskManager,
mMockWindowManagerService,
mMockWindowManagerInternal,
@@ -429,6 +446,214 @@
}
@Test
+ public void gameProcessStopped_soleProcess_destroysGameSession() throws Exception {
+ int gameProcessId = 1000;
+
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+
+ // Death of the sole game process destroys the game session.
+ dispatchProcessDied(gameProcessId);
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ }
+
+ @Test
+ public void gameProcessStopped_soleProcess_destroysMultipleGameSessionsForSamePackage()
+ throws Exception {
+ int gameProcessId = 1000;
+
+ mGameServiceProviderInstance.start();
+
+ // Multiple tasks exist for the same package.
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ startTask(11, GAME_A_MAIN_ACTIVITY);
+ startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+ mFakeGameService.requestCreateGameSession(11);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+ FakeGameSession gameSession11 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(11)
+ .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+ assertThat(gameSession11.mIsDestroyed).isFalse();
+
+ // Death of the sole game process destroys both game sessions.
+ dispatchProcessDied(gameProcessId);
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isTrue();
+ }
+
+ @Test
+ public void gameProcessStopped_multipleProcesses_gameSessionDestroyedWhenAllDead()
+ throws Exception {
+ int firstGameProcessId = 1000;
+ int secondGameProcessId = 1001;
+
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
+ startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+
+ // Death of the first process (with the second one still alive) does not destroy the game
+ // session.
+ dispatchProcessDied(firstGameProcessId);
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+
+ // Death of the second process does destroy the game session.
+ dispatchProcessDied(secondGameProcessId);
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ }
+
+ @Test
+ public void gameProcessCreatedAfterInitialProcessDead_newGameSessionCreated() throws Exception {
+ int firstGameProcessId = 1000;
+ int secondGameProcessId = 1000;
+
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+
+ // After the first game process dies, the game session should be destroyed.
+ dispatchProcessDied(firstGameProcessId);
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+
+ // However, when a new process for the game starts, a new game session should be created.
+ startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
+ // Verify that a new pending game session is created for the game's taskId.
+ assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10));
+ }
+
+ @Test
+ public void gameProcessCreatedAfterInitialProcessDead_multipleGameSessionsCreatedSamePackage()
+ throws Exception {
+ int firstGameProcessId = 1000;
+ int secondGameProcessId = 1000;
+
+ mGameServiceProviderInstance.start();
+
+ // Multiple tasks exist for the same package.
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ startTask(11, GAME_A_MAIN_ACTIVITY);
+ startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+
+ mFakeGameService.requestCreateGameSession(10);
+ mFakeGameService.requestCreateGameSession(11);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+ FakeGameSession gameSession11 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(11)
+ .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+ assertThat(gameSession11.mIsDestroyed).isFalse();
+
+ // After the first game process dies, both game sessions for the package should be
+ // destroyed.
+ dispatchProcessDied(firstGameProcessId);
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isTrue();
+
+ // However, when a new process for the game starts, new game sessions for the same
+ // package should be created.
+ startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
+ // Verify that new pending game sessions were created for each of the game's taskIds.
+ assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10));
+ assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(11));
+ }
+
+ @Test
+ public void gameProcessStarted_gameSessionNotRequested_doesNothing() throws Exception {
+ int gameProcessId = 1000;
+
+ mGameServiceProviderInstance.start();
+
+ // A game task and process are started, but requestCreateGameSession is never called.
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+
+ // No game session should be created.
+ assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
+ }
+
+ @Test
+ public void processActivityAndDeath_notForGame_gameSessionUnaffected() throws Exception {
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ // Process activity for a process without a known package is ignored.
+ startProcessForPackage(1000, /*packageName=*/ null);
+ dispatchProcessActivity(1000);
+ dispatchProcessDied(1000);
+
+ // Process activity for a process with a different package is ignored
+ startProcessForPackage(1001, GAME_B_PACKAGE);
+ dispatchProcessActivity(1001);
+ dispatchProcessDied(1001);
+
+ // Death of a process for which there was no activity is ignored
+ dispatchProcessDied(1002);
+
+ // Despite all the process activity and death, the game session is not destroyed.
+ assertThat(gameSession10.mIsDestroyed).isFalse();
+ }
+
+ @Test
public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() {
mGameServiceProviderInstance.start();
@@ -900,7 +1125,8 @@
mFakeGameSessionService.getCapturedCreateInvocations())
.mGameSessionController.restartGame(11);
- verifyZeroInteractions(mMockActivityManager);
+ verify(mMockActivityManager).registerProcessObserver(any());
+ verifyNoMoreInteractions(mMockActivityManager);
assertThat(mMockContext.getLastStartedIntent()).isNull();
}
@@ -932,7 +1158,6 @@
dispatchTaskRemoved(taskId);
}
-
private void dispatchTaskRemoved(int taskId) {
dispatchTaskChangeEvent(taskStackListener -> {
taskStackListener.onTaskRemoved(taskId);
@@ -958,6 +1183,37 @@
}
}
+ private void startProcessForPackage(int processId, @Nullable String packageName) {
+ if (packageName != null) {
+ when(mMockActivityManagerInternal.getPackageNameByPid(processId)).thenReturn(
+ packageName);
+ }
+
+ dispatchProcessActivity(processId);
+ }
+
+ private void dispatchProcessActivity(int processId) {
+ dispatchProcessChangedEvent(processObserver -> {
+ // Neither uid nor foregroundActivities are used by the implementation being tested.
+ processObserver.onForegroundActivitiesChanged(processId, /*uid=*/
+ 0, /*foregroundActivities=*/ false);
+ });
+ }
+
+ private void dispatchProcessDied(int processId) {
+ dispatchProcessChangedEvent(processObserver -> {
+ // The uid param is not used by the implementation being tested.
+ processObserver.onProcessDied(processId, /*uid=*/ 0);
+ });
+ }
+
+ private void dispatchProcessChangedEvent(
+ ThrowingConsumer<IProcessObserver> processObserverConsumer) {
+ for (IProcessObserver processObserver : mProcessObservers) {
+ processObserverConsumer.accept(processObserver);
+ }
+ }
+
private void mockPermissionGranted(String permission) {
mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 4de15c8..928c76d 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -330,11 +330,12 @@
@Test
public void testPriorityPersisted() throws Exception {
- final JobInfo.Builder b = new Builder(92, mComponent)
+ final JobInfo job = new Builder(92, mComponent)
.setOverrideDeadline(5000)
.setPriority(JobInfo.PRIORITY_MIN)
- .setPersisted(true);
- final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+ .setPersisted(true)
+ .build();
+ final JobStatus js = JobStatus.createFromJobInfo(job, SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(js);
waitForPendingIo();
@@ -342,7 +343,7 @@
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
final JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
assertEquals("Priority not correctly persisted.",
- JobInfo.PRIORITY_MIN, loaded.getEffectivePriority());
+ JobInfo.PRIORITY_MIN, job.getPriority());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 21c09a0..1d10b8a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -217,7 +217,7 @@
}
@Override
- protected boolean isCredentialSharedWithParent(int userId) {
+ protected boolean isCredentialSharableWithParent(int userId) {
UserInfo userInfo = mUserManager.getUserInfo(userId);
return userInfo.isCloneProfile() || userInfo.isManagedProfile();
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2b34bc2..f4ab3db 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -31,11 +31,11 @@
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -1103,6 +1103,59 @@
assertThat(countDownLatch.getCount(), is(0L));
}
+ @Test
+ public void testRegisterAndRemoveAppId() throws PackageManagerException {
+ // Test that the first new app UID should start from FIRST_APPLICATION_UID
+ final Settings settings = makeSettings();
+ final PackageSetting ps = createPackageSetting("com.foo");
+ assertTrue(settings.registerAppIdLPw(ps, false));
+ assertEquals(10000, ps.getAppId());
+ // Set up existing app IDs: 10000, 10001, 10003
+ final PackageSetting ps1 = createPackageSetting("com.foo1");
+ ps1.setAppId(10001);
+ final PackageSetting ps2 = createPackageSetting("com.foo2");
+ ps2.setAppId(10003);
+ final PackageSetting ps3 = createPackageSetting("com.foo3");
+ assertEquals(0, ps3.getAppId());
+ assertTrue(settings.registerAppIdLPw(ps1, false));
+ assertTrue(settings.registerAppIdLPw(ps2, false));
+ assertTrue(settings.registerAppIdLPw(ps3, false));
+ assertEquals(10001, ps1.getAppId());
+ assertEquals(10003, ps2.getAppId());
+ // Expecting the new one to start with the next available uid
+ assertEquals(10002, ps3.getAppId());
+ // Remove and insert a new one and the new one should not reuse the same uid
+ settings.removeAppIdLPw(10002);
+ final PackageSetting ps4 = createPackageSetting("com.foo4");
+ assertTrue(settings.registerAppIdLPw(ps4, false));
+ assertEquals(10004, ps4.getAppId());
+ // Keep adding more
+ final PackageSetting ps5 = createPackageSetting("com.foo5");
+ assertTrue(settings.registerAppIdLPw(ps5, false));
+ assertEquals(10005, ps5.getAppId());
+ // Remove the last one and the new one should use incremented uid
+ settings.removeAppIdLPw(10005);
+ final PackageSetting ps6 = createPackageSetting("com.foo6");
+ assertTrue(settings.registerAppIdLPw(ps6, false));
+ assertEquals(10006, ps6.getAppId());
+ }
+
+ /**
+ * Test replacing a PackageSetting with a SharedUserSetting in mAppIds
+ */
+ @Test
+ public void testAddPackageSetting() throws PackageManagerException {
+ final Settings settings = makeSettings();
+ final SharedUserSetting sus1 = new SharedUserSetting(
+ "TestUser", 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ sus1.mAppId = 10001;
+ final PackageSetting ps1 = createPackageSetting("com.foo");
+ ps1.setAppId(10001);
+ assertTrue(settings.registerAppIdLPw(ps1, false));
+ settings.addPackageSettingLPw(ps1, sus1);
+ assertSame(sus1, settings.getSharedUserSettingLPr(ps1));
+ }
+
private void verifyUserState(PackageUserState userState,
boolean notLaunched, boolean stopped, boolean installed) {
assertThat(userState.getEnabledState(), is(0));
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 1442f1c..d76a1de 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -23,9 +23,11 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.eq;
@@ -37,6 +39,7 @@
import android.Manifest;
import android.app.ActivityManagerInternal;
import android.app.StatusBarManager;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Intent;
import android.content.om.IOverlayManager;
@@ -62,10 +65,13 @@
import com.android.server.policy.GlobalActionsProvider;
import com.android.server.wm.ActivityTaskManagerInternal;
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
@@ -73,6 +79,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
@RunWith(JUnit4.class)
public class StatusBarManagerServiceTest {
@@ -88,6 +95,9 @@
public final TestableContext mContext =
new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+ @Rule
+ public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
+
@Mock
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@Mock
@@ -127,6 +137,7 @@
when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar);
when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME);
+ mockHandleIncomingUser();
mStatusBarManagerService = new StatusBarManagerService(mContext);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -142,6 +153,80 @@
}
@Test
+ @CoreCompatChangeRule.EnableCompatChanges(
+ {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
+ public void testRequestActive_changeEnabled_OKCall() throws RemoteException {
+ int user = 0;
+ mockEverything(user);
+ mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user);
+
+ verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges(
+ {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
+ public void testRequestActive_changeEnabled_differentPackage_fail() throws RemoteException {
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0L, mContext.getUserId()))
+ .thenReturn(Binder.getCallingUid() + 1);
+ try {
+ mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, 0);
+ fail("Should cause security exception");
+ } catch (SecurityException e) { }
+ verify(mMockStatusBar, never()).requestTileServiceListeningState(TEST_COMPONENT);
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges(
+ {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
+ public void testRequestActive_changeEnabled_notCurrentUser_fail() throws RemoteException {
+ mockUidCheck();
+ int user = 0;
+ mockCurrentUserCheck(user);
+ try {
+ mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user + 1);
+ fail("Should cause illegal argument exception");
+ } catch (IllegalArgumentException e) { }
+
+ // Do not call into SystemUI
+ verify(mMockStatusBar, never()).requestTileServiceListeningState(TEST_COMPONENT);
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges(
+ {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
+ public void testRequestActive_changeDisabled_pass() throws RemoteException {
+ int user = 0;
+ mockEverything(user);
+ mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user);
+
+ verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges(
+ {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
+ public void testRequestActive_changeDisabled_differentPackage_pass() throws RemoteException {
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0L, mContext.getUserId()))
+ .thenReturn(Binder.getCallingUid() + 1);
+ mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, 0);
+
+ verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges(
+ {StatusBarManagerService.REQUEST_LISTENING_MUST_MATCH_PACKAGE})
+ public void testRequestActive_changeDisabled_notCurrentUser_pass() throws RemoteException {
+ mockUidCheck();
+ int user = 0;
+ mockCurrentUserCheck(user);
+ mStatusBarManagerService.requestTileServiceListeningState(TEST_COMPONENT, user + 1);
+
+ verify(mMockStatusBar).requestTileServiceListeningState(TEST_COMPONENT);
+ }
+
+ @Test
public void testHandleIncomingUserCalled() {
int fakeUser = 17;
try {
@@ -252,7 +337,7 @@
mockCurrentUserCheck(user);
IntentMatcher im = new IntentMatcher(
new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
- when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
eq(user), anyInt())).thenReturn(null);
Callback callback = new Callback();
@@ -272,7 +357,7 @@
IntentMatcher im = new IntentMatcher(
new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
- when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
eq(user), anyInt())).thenReturn(r);
when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
Binder.getCallingUid(), user)).thenReturn(
@@ -294,7 +379,7 @@
IntentMatcher im = new IntentMatcher(
new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
- when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
eq(user), anyInt())).thenReturn(r);
when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
Binder.getCallingUid(), user)).thenReturn(
@@ -318,7 +403,7 @@
IntentMatcher im = new IntentMatcher(
new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
- when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
eq(user), anyInt())).thenReturn(r);
when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
Binder.getCallingUid(), user)).thenReturn(
@@ -342,7 +427,7 @@
IntentMatcher im = new IntentMatcher(
new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
- when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
eq(user), anyInt())).thenReturn(r);
when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
Binder.getCallingUid(), user)).thenReturn(
@@ -641,7 +726,7 @@
}
private void mockUidCheck(String packageName) {
- when(mPackageManagerInternal.getPackageUid(eq(packageName), anyInt(), anyInt()))
+ when(mPackageManagerInternal.getPackageUid(eq(packageName), anyLong(), anyInt()))
.thenReturn(Binder.getCallingUid());
}
@@ -667,7 +752,7 @@
IntentMatcher im = new IntentMatcher(
new Intent(TileService.ACTION_QS_TILE).setComponent(componentName));
- when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0L),
eq(user), anyInt())).thenReturn(r);
when(mPackageManagerInternal.getComponentEnabledSetting(componentName,
Binder.getCallingUid(), user)).thenReturn(
@@ -679,6 +764,15 @@
PROCESS_STATE_TOP);
}
+ private void mockHandleIncomingUser() {
+ when(mActivityManagerInternal.handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(),
+ anyInt(), anyString(), anyString())).thenAnswer(
+ (Answer<Integer>) invocation -> {
+ return invocation.getArgument(2); // same user
+ }
+ );
+ }
+
private void mockEverything(int user) {
mockUidCheck();
mockCurrentUserCheck(user);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 18d3f3d..210d2fa 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -62,7 +62,10 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.intThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -76,11 +79,13 @@
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
@@ -118,6 +123,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* Unit test for AppStandbyController.
@@ -421,8 +427,31 @@
pib.packageName = PACKAGE_BACKGROUND_LOCATION;
packages.add(pib);
- doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
+ // Set up getInstalledPackagesAsUser().
+ doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(),
+ anyInt());
+
+ // Set up getInstalledPackagesAsUser() for "MATCH_ONLY_SYSTEM"
+ doReturn(
+ packages.stream().filter(pinfo -> pinfo.applicationInfo.isSystemApp())
+ .collect(Collectors.toList())
+ ).when(mockPm).getInstalledPackagesAsUser(
+ intThat(i -> (i & PackageManager.MATCH_SYSTEM_ONLY) != 0),
+ anyInt());
+
+ // Set up queryIntentActivitiesAsUser()
+ final ArrayList<ResolveInfo> systemFrontDoorActivities = new ArrayList<>();
+ final ResolveInfo frontDoorActivity = new ResolveInfo();
+ frontDoorActivity.activityInfo = new ActivityInfo();
+ frontDoorActivity.activityInfo.packageName = pis.packageName;
+ systemFrontDoorActivities.add(frontDoorActivity);
+ doReturn(systemFrontDoorActivities).when(mockPm)
+ .queryIntentActivitiesAsUser(any(Intent.class),
+ intThat(i -> (i & PackageManager.MATCH_SYSTEM_ONLY) != 0),
+ anyInt());
+
+ // Set up other APIs.
try {
for (int i = 0; i < packages.size(); ++i) {
PackageInfo pkg = packages.get(i);
@@ -489,6 +518,7 @@
@Before
public void setUp() throws Exception {
+ LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
LocalServices.addService(
UsageStatsManagerInternal.class, mock(UsageStatsManagerInternal.class));
MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 50151bf..46b47f4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -88,7 +88,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true);
+ mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true, false);
PackageInfo testPkgInfo = new PackageInfo();
testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS };
when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
@@ -100,7 +100,7 @@
public void testMethodsThrowIfMigrationDisabled() throws IllegalAccessException,
InvocationTargetException {
PermissionHelper permHelper =
- new PermissionHelper(mPmi, mPackageManager, mPermManager, false);
+ new PermissionHelper(mPmi, mPackageManager, mPermManager, false, false);
Method[] allMethods = PermissionHelper.class.getDeclaredMethods();
for (Method method : allMethods) {
@@ -302,6 +302,26 @@
}
@Test
+ public void testSetNotificationPermission_pkgPerm_grantedByDefaultPermSet_allUserSet()
+ throws Exception {
+ mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true, true);
+ when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ .thenReturn(PERMISSION_DENIED);
+ when(mPermManager.getPermissionFlags(anyString(),
+ eq(Manifest.permission.POST_NOTIFICATIONS),
+ anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT);
+ PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission(
+ "pkg", 10, true, false);
+
+ mPermissionHelper.setNotificationPermission(pkgPerm);
+ verify(mPermManager).grantRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_REVIEW_REQUIRED,
+ FLAG_PERMISSION_USER_SET, true, 10);
+ }
+
+ @Test
public void testSetNotificationPermission_revokeUserSet() throws Exception {
when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 0debdfa..c615866 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -359,17 +359,11 @@
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment()
throws RemoteException {
mController.registerOrganizer(mIOrganizer);
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final int uid = Binder.getCallingUid();
- activity.info.applicationInfo.uid = uid;
- activity.getTask().effectiveUid = uid;
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
- final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
- mOrganizerToken, fragmentToken, activity.token).build();
- mOrganizer.applyTransaction(mTransaction);
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
- mTransaction.createTaskFragment(params);
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
mTransaction.startActivityInTaskFragment(
mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
@@ -381,7 +375,7 @@
final TaskFragment taskFragment = mAtm.mWindowOrganizerController
.getTaskFragment(fragmentToken);
assertNotNull(taskFragment);
- assertEquals(activity.getTask(), taskFragment.getTask());
+ assertEquals(ownerActivity.getTask(), taskFragment.getTask());
}
@Test
@@ -523,4 +517,43 @@
mController.dispatchPendingEvents();
verify(mOrganizer).onTaskFragmentInfoChanged(any());
}
+
+ /**
+ * When an embedded {@link TaskFragment} is removed, we should clean up the reference in the
+ * {@link WindowOrganizerController}.
+ */
+ @Test
+ public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment()
+ throws RemoteException {
+ mController.registerOrganizer(mIOrganizer);
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ final TaskFragment taskFragment = mAtm.mWindowOrganizerController
+ .getTaskFragment(fragmentToken);
+
+ assertNotNull(taskFragment);
+
+ taskFragment.removeImmediately();
+
+ assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ /**
+ * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
+ * {@link WindowOrganizerController#applyTransaction} to apply the transaction,
+ */
+ private void createTaskFragmentFromOrganizer(WindowContainerTransaction wct,
+ ActivityRecord ownerActivity, IBinder fragmentToken) {
+ final int uid = Binder.getCallingUid();
+ ownerActivity.info.applicationInfo.uid = uid;
+ ownerActivity.getTask().effectiveUid = uid;
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, ownerActivity.token).build();
+ mOrganizer.applyTransaction(wct);
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ wct.createTaskFragment(params);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index fe41734..636c6bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -146,6 +146,27 @@
}
@Test
+ public void testRemoveContainer_multipleNestedTasks() {
+ final Task rootTask = createTask(mDisplayContent);
+ rootTask.mCreatedByOrganizer = true;
+ final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+ final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+ final ActivityRecord activity1 = createActivityRecord(task1);
+ final ActivityRecord activity2 = createActivityRecord(task2);
+ activity1.setVisible(false);
+
+ // All activities under the root task should be finishing.
+ rootTask.remove(true /* withTransition */, "test");
+ assertTrue(activity1.finishing);
+ assertTrue(activity2.finishing);
+
+ // After all activities activities are destroyed, the root task should also be removed.
+ activity1.removeImmediately();
+ activity2.removeImmediately();
+ assertFalse(rootTask.isAttached());
+ }
+
+ @Test
public void testRemoveContainer_deferRemoval() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 68e90e1..08320f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -161,7 +161,7 @@
@Test
public void testDismissKeyguardCanWakeUp() {
doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString());
- doReturn(true).when(mWm.mAtmService).isDreaming();
+ doReturn(true).when(mWm.mAtmService.mKeyguardController).isShowingDream();
doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
mWm.dismissKeyguard(null, "test-dismiss-keyguard");
verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a597fc6..5a95210 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -23,6 +23,25 @@
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull;
@@ -92,13 +111,25 @@
static final boolean DEBUG = false;
// TODO: These constants need to be refined.
- private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
+ private static final long VALIDATION_TIMEOUT_MILLIS = 4000;
private static final long MAX_UPDATE_TIMEOUT_MILLIS = 6000;
private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
+ // Hotword metrics
+ private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
+ private static final int METRICS_INIT_UNKNOWN_NO_VALUE =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
+ private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
+ private static final int METRICS_INIT_CALLBACK_STATE_ERROR =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
+ private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
+
private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
@@ -107,17 +138,20 @@
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
private final IHotwordRecognitionStatusCallback mCallback;
+ private final int mDetectorType;
final Object mLock;
final int mVoiceInteractionServiceUid;
final ComponentName mDetectionComponentName;
final int mUser;
final Context mContext;
+
volatile HotwordDetectionServiceIdentity mIdentity;
private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
private Instant mLastRestartInstant;
private ScheduledFuture<?> mCancellationTaskFuture;
+ private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture;
private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
/** Identity used for attributing app ops when delivering data to the Interactor. */
@@ -133,7 +167,6 @@
private @NonNull ServiceConnection mRemoteHotwordDetectionService;
private IBinder mAudioFlinger;
private boolean mDebugHotwordLogging = false;
- private final int mDetectorType;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -169,6 +202,8 @@
Slog.v(TAG, "Time to restart the process, TTL has passed");
synchronized (mLock) {
restartProcessLocked();
+ HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
}
}, 30, 30, TimeUnit.MINUTES);
}
@@ -201,6 +236,8 @@
// We restart the process instead of simply sending over the new binder, to avoid race
// conditions with audio reading in the service.
restartProcessLocked();
+ HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
}
}
@@ -220,26 +257,32 @@
future.complete(null);
if (mUpdateStateAfterStartFinished.getAndSet(true)) {
Slog.w(TAG, "call callback after timeout");
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
+ mVoiceInteractionServiceUid);
return;
}
- int status = bundle != null ? bundle.getInt(
- KEY_INITIALIZATION_STATUS,
- INITIALIZATION_STATUS_UNKNOWN)
- : INITIALIZATION_STATUS_UNKNOWN;
- // Add the protection to avoid unexpected status
- if (status > HotwordDetectionService.getMaxCustomInitializationStatus()
- && status != INITIALIZATION_STATUS_UNKNOWN) {
- status = INITIALIZATION_STATUS_UNKNOWN;
- }
+ Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
+ int status = statusResultPair.first;
+ int initResultMetricsResult = statusResultPair.second;
try {
mCallback.onStatusReported(status);
+ HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
+ initResultMetricsResult);
} catch (RemoteException e) {
+ // TODO: Add a new atom for RemoteException case, the error doesn't very
+ // correct here
Slog.w(TAG, "Failed to report initialization status: " + e);
+ HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
+ METRICS_INIT_CALLBACK_STATE_ERROR);
}
}
};
try {
service.updateState(options, sharedMemory, statusCallback);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
+ mVoiceInteractionServiceUid);
} catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor
Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
@@ -254,8 +297,12 @@
}
try {
mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
+ HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
+ METRICS_INIT_UNKNOWN_TIMEOUT);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
+ HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
+ METRICS_INIT_CALLBACK_STATE_ERROR);
}
} else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err);
@@ -265,6 +312,23 @@
});
}
+ private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) {
+ if (bundle == null) {
+ return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE);
+ }
+ int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN);
+ if (status > HotwordDetectionService.getMaxCustomInitializationStatus()
+ && status != INITIALIZATION_STATUS_UNKNOWN) {
+ return new Pair<>(INITIALIZATION_STATUS_UNKNOWN,
+ METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE);
+ }
+ // TODO: should guard against negative here
+ int metricsResult = status == INITIALIZATION_STATUS_UNKNOWN
+ ? METRICS_INIT_CALLBACK_STATE_ERROR
+ : METRICS_INIT_CALLBACK_STATE_SUCCESS;
+ return new Pair<>(status, metricsResult);
+ }
+
private boolean isBound() {
synchronized (mLock) {
return mRemoteHotwordDetectionService.isBound();
@@ -481,12 +545,31 @@
Slog.d(TAG, "onDetected");
}
synchronized (mLock) {
+ // TODO: If the dsp trigger comes in after the timeout, we will log both events.
+ // Because we don't enforce the timeout yet. We should add some synchronizations
+ // within the runnable to prevent the race condition to log both events.
+ if (mCancellationKeyPhraseDetectionFuture != null) {
+ mCancellationKeyPhraseDetectionFuture.cancel(true);
+ }
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onDetected due to a process restart");
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION);
return;
}
mValidatingDspTrigger = false;
- enforcePermissionsForDataDelivery();
+ try {
+ enforcePermissionsForDataDelivery();
+ } catch (SecurityException e) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION);
+ throw e;
+ }
externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -504,8 +587,17 @@
Slog.d(TAG, "onRejected");
}
synchronized (mLock) {
+ if (mCancellationKeyPhraseDetectionFuture != null) {
+ mCancellationKeyPhraseDetectionFuture.cancel(true);
+ }
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onRejected due to a process restart");
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION);
return;
}
mValidatingDspTrigger = false;
@@ -520,11 +612,20 @@
synchronized (mLock) {
mValidatingDspTrigger = true;
mRemoteHotwordDetectionService.run(
- service -> service.detectFromDspSource(
- recognitionEvent,
- recognitionEvent.getCaptureFormat(),
- VALIDATION_TIMEOUT_MILLIS,
- internalCallback));
+ service -> {
+ // TODO: avoid allocate every time
+ mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
+ () -> HotwordMetricsLogger
+ .writeKeyphraseTriggerEvent(mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT),
+ VALIDATION_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS);
+ service.detectFromDspSource(
+ recognitionEvent,
+ recognitionEvent.getCaptureFormat(),
+ VALIDATION_TIMEOUT_MILLIS,
+ internalCallback);
+ });
}
}
@@ -625,10 +726,16 @@
}
final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
if (useHotwordDetectionService) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
mRecognitionEvent = recognitionEvent;
mHotwordDetectionConnection.detectFromDspSource(
recognitionEvent, mExternalCallback);
} else {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
}
}
@@ -793,6 +900,7 @@
private boolean mRespectServiceConnectionStatusChanged = true;
private boolean mIsBound = false;
+ private boolean mIsLoggedFirstConnect = false;
ServiceConnection(@NonNull Context context,
@NonNull Intent intent, int bindingFlags, int userId,
@@ -816,6 +924,12 @@
return;
}
mIsBound = connected;
+ if (connected && !mIsLoggedFirstConnect) {
+ mIsLoggedFirstConnect = true;
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
+ mVoiceInteractionServiceUid);
+ }
}
}
@@ -846,13 +960,25 @@
protected boolean bindService(
@NonNull android.content.ServiceConnection serviceConnection) {
try {
- return mContext.bindIsolatedService(
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
+ mVoiceInteractionServiceUid);
+ boolean bindResult = mContext.bindIsolatedService(
mIntent,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
"hotword_detector_" + mInstanceNumber,
mExecutor,
serviceConnection);
+ if (!bindResult) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
+ mVoiceInteractionServiceUid);
+ }
+ return bindResult;
} catch (IllegalArgumentException e) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
+ mVoiceInteractionServiceUid);
Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
return false;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index de0b960..940aed3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -16,6 +16,24 @@
package com.android.server.voiceinteraction;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+
+import android.service.voice.HotwordDetector;
+
import com.android.internal.util.FrameworkStatsLog;
/**
@@ -23,6 +41,13 @@
*/
public final class HotwordMetricsLogger {
+ private static final int METRICS_INIT_DETECTOR_SOFTWARE =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+ private static final int METRICS_INIT_DETECTOR_DSP =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+ private static final int METRICS_INIT_NORMAL_DETECTOR =
+ HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+
private HotwordMetricsLogger() {
// Class only contains static utility functions, and should not be instantiated
}
@@ -31,39 +56,99 @@
* Logs information related to create hotword detector.
*/
public static void writeDetectorCreateEvent(int detectorType, boolean isCreated, int uid) {
- FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED, detectorType,
- isCreated, uid);
+ int metricsDetectorType = getCreateMetricsDetectorType(detectorType);
+ FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED,
+ metricsDetectorType, isCreated, uid);
}
/**
* Logs information related to hotword detection service init result.
*/
public static void writeServiceInitResultEvent(int detectorType, int result) {
+ int metricsDetectorType = getInitMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED,
- detectorType, result);
+ metricsDetectorType, result);
}
/**
* Logs information related to hotword detection service restarting.
*/
public static void writeServiceRestartEvent(int detectorType, int reason) {
+ int metricsDetectorType = getRestartMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED,
- detectorType, reason);
+ metricsDetectorType, reason);
}
/**
* Logs information related to keyphrase trigger.
*/
public static void writeKeyphraseTriggerEvent(int detectorType, int result) {
+ int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED,
- detectorType, result);
+ metricsDetectorType, result);
}
/**
* Logs information related to hotword detector events.
*/
public static void writeDetectorEvent(int detectorType, int event, int uid) {
+ int metricsDetectorType = getDetectorMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS,
- detectorType, event, uid);
+ metricsDetectorType, event, uid);
+ }
+
+ private static int getCreateMetricsDetectorType(int detectorType) {
+ switch (detectorType) {
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+ default:
+ return HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+ }
+ }
+
+ private static int getRestartMetricsDetectorType(int detectorType) {
+ switch (detectorType) {
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+ default:
+ return HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+ }
+ }
+
+ private static int getInitMetricsDetectorType(int detectorType) {
+ switch (detectorType) {
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return METRICS_INIT_DETECTOR_SOFTWARE;
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return METRICS_INIT_DETECTOR_DSP;
+ default:
+ return METRICS_INIT_NORMAL_DETECTOR;
+ }
+ }
+
+ private static int getKeyphraseMetricsDetectorType(int detectorType) {
+ switch (detectorType) {
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+ default:
+ return HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
+ }
+ }
+
+ private static int getDetectorMetricsDetectorType(int detectorType) {
+ switch (detectorType) {
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+ default:
+ return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__NORMAL_DETECTOR;
+ }
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index fb4d73c..0519873 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -462,24 +462,33 @@
IHotwordRecognitionStatusCallback callback,
int detectorType) {
Slog.v(TAG, "updateStateLocked");
+ int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
if (mHotwordDetectionComponentName == null) {
Slog.w(TAG, "Hotword detection service name not found");
+ logDetectorCreateEventIfNeeded(callback, detectorType, false,
+ voiceInteractionServiceUid);
throw new IllegalStateException("Hotword detection service name not found");
}
ServiceInfo hotwordDetectionServiceInfo = getServiceInfoLocked(
mHotwordDetectionComponentName, mUser);
if (hotwordDetectionServiceInfo == null) {
Slog.w(TAG, "Hotword detection service info not found");
+ logDetectorCreateEventIfNeeded(callback, detectorType, false,
+ voiceInteractionServiceUid);
throw new IllegalStateException("Hotword detection service info not found");
}
if (!isIsolatedProcessLocked(hotwordDetectionServiceInfo)) {
Slog.w(TAG, "Hotword detection service not in isolated process");
+ logDetectorCreateEventIfNeeded(callback, detectorType, false,
+ voiceInteractionServiceUid);
throw new IllegalStateException("Hotword detection service not in isolated process");
}
if (!Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals(
hotwordDetectionServiceInfo.permission)) {
Slog.w(TAG, "Hotword detection service does not require permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
+ logDetectorCreateEventIfNeeded(callback, detectorType, false,
+ voiceInteractionServiceUid);
throw new SecurityException("Hotword detection service does not require permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
}
@@ -488,17 +497,23 @@
mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Voice interaction service should not hold permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
+ logDetectorCreateEventIfNeeded(callback, detectorType, false,
+ voiceInteractionServiceUid);
throw new SecurityException("Voice interaction service should not hold permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
}
if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
Slog.w(TAG, "Can't set sharedMemory to be read-only");
+ logDetectorCreateEventIfNeeded(callback, detectorType, false,
+ voiceInteractionServiceUid);
throw new IllegalStateException("Can't set sharedMemory to be read-only");
}
mDetectorType = detectorType;
+ logDetectorCreateEventIfNeeded(callback, detectorType, true,
+ voiceInteractionServiceUid);
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
@@ -509,6 +524,15 @@
}
}
+ private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
+ int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
+ if (callback != null) {
+ HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+ voiceInteractionServiceUid);
+ }
+
+ }
+
public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 27d423b..bce6809 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -3171,9 +3171,14 @@
*
* {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
*
+ * @param connectionManagerPhoneAccount The connection manager account to use for managing
+ * this call
+ * @param request Details about the outgoing call
+ * @return The {@code Connection} object to satisfy this call, or the result of an invocation
+ * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call
* @hide
*/
- @SystemApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public @Nullable Connection onCreateUnknownConnection(
@NonNull PhoneAccountHandle connectionManagerPhoneAccount,
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1eb391d..5ef22de 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16790,7 +16790,10 @@
* Callback to listen for when the set of packages with carrier privileges for a SIM changes.
*
* @hide
+ * @deprecated Use {@link CarrierPrivilegesCallback} instead. This API will be removed soon
+ * prior to API finalization.
*/
+ @Deprecated
@SystemApi
public interface CarrierPrivilegesListener {
/**
@@ -16810,6 +16813,54 @@
}
/**
+ * Callbacks to listen for when the set of packages with carrier privileges for a SIM changes.
+ *
+ * <p>Of note, when multiple callbacks are registered, they may be triggered one after another.
+ * The ordering of them is not guaranteed and thus should not be depend on.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface CarrierPrivilegesCallback {
+ /**
+ * Called when the set of packages with carrier privileges has changed.
+ *
+ * <p>Of note, this callback will <b>not</b> be fired if a carrier triggers a SIM profile
+ * switch and the same set of packages remains privileged after the switch.
+ *
+ * <p>At registration, the callback will receive the current set of privileged packages.
+ *
+ * @param privilegedPackageNames The updated set of package names that have carrier
+ * privileges
+ * @param privilegedUids The updated set of UIDs that have carrier privileges
+ */
+ void onCarrierPrivilegesChanged(
+ @NonNull Set<String> privilegedPackageNames, @NonNull Set<Integer> privilegedUids);
+
+ /**
+ * Called when the {@link CarrierService} for the current user profile has changed.
+ *
+ * <p>This method does nothing by default. Clients that are interested in the carrier
+ * service change should override this method to get package name and UID info.
+ *
+ * <p>At registration, the callback will receive the current carrier service info.
+ *
+ * <p>Of note, this callback will <b>not</b> be fired if a carrier triggers a SIM profile
+ * switch and the same carrier service remains after switch.
+ *
+ * @param carrierServicePackageName package name of the {@link CarrierService}. May be
+ * {@code null} when no carrier service is detected.
+ * @param carrierServiceUid UID of the {@link CarrierService}. May be
+ * {@link android.os.Process#INVALID_UID} if no carrier
+ * service is detected.
+ */
+ default void onCarrierServiceChanged(
+ @Nullable String carrierServicePackageName, int carrierServiceUid) {
+ // do nothing by default
+ }
+ }
+
+ /**
* Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
* receive callbacks when the set of packages with carrier privileges changes. The callback will
* immediately be called with the latest state.
@@ -16818,7 +16869,10 @@
* @param executor The executor where {@code listener} will be invoked
* @param listener The callback to register
* @hide
+ * @deprecated Use {@link #unregisterCarrierPrivilegesCallback} instead. This API will be
+ * removed prior to API finalization.
*/
+ @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void addCarrierPrivilegesListener(
@@ -16842,7 +16896,10 @@
* Unregisters an existing {@link CarrierPrivilegesListener}.
*
* @hide
+ * @deprecated Use {@link #unregisterCarrierPrivilegesCallback} instead. This API will be
+ * removed prior to API finalization.
*/
+ @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
@@ -16890,4 +16947,53 @@
ex.rethrowAsRuntimeException();
}
}
+
+ /**
+ * Registers a {@link CarrierPrivilegesCallback} on the given {@code logicalSlotIndex} to
+ * receive callbacks when the set of packages with carrier privileges changes. The callback will
+ * immediately be called with the latest state.
+ *
+ * @param logicalSlotIndex The SIM slot to listen on
+ * @param executor The executor where {@code callback} will be invoked
+ * @param callback The callback to register
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void registerCarrierPrivilegesCallback(
+ int logicalSlotIndex,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CarrierPrivilegesCallback callback) {
+ if (mContext == null) {
+ throw new IllegalStateException("Telephony service is null");
+ } else if (executor == null || callback == null) {
+ throw new IllegalArgumentException(
+ "CarrierPrivilegesCallback and executor must be non-null");
+ }
+ mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (mTelephonyRegistryMgr == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ mTelephonyRegistryMgr.addCarrierPrivilegesCallback(logicalSlotIndex, executor, callback);
+ }
+
+ /**
+ * Unregisters an existing {@link CarrierPrivilegesCallback}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void unregisterCarrierPrivilegesCallback(@NonNull CarrierPrivilegesCallback callback) {
+ if (mContext == null) {
+ throw new IllegalStateException("Telephony service is null");
+ } else if (callback == null) {
+ throw new IllegalArgumentException("CarrierPrivilegesCallback must be non-null");
+ }
+ mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (mTelephonyRegistryMgr == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ mTelephonyRegistryMgr.removeCarrierPrivilegesCallback(callback);
+ }
}
diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
index 493ad5e..1ccb464 100644
--- a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
@@ -48,6 +48,9 @@
@Override
public void sendMessage(SipMessage sipMessage, long configVersion) {
SipDelegate d = mDelegate;
+ if (d == null) {
+ return;
+ }
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> d.sendMessage(sipMessage, configVersion));
@@ -59,6 +62,9 @@
@Override
public void notifyMessageReceived(String viaTransactionId) {
SipDelegate d = mDelegate;
+ if (d == null) {
+ return;
+ }
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> d.notifyMessageReceived(viaTransactionId));
@@ -71,6 +77,9 @@
@Override
public void notifyMessageReceiveError(String viaTransactionId, int reason) {
SipDelegate d = mDelegate;
+ if (d == null) {
+ return;
+ }
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> d.notifyMessageReceiveError(viaTransactionId, reason));
@@ -83,6 +92,9 @@
@Override
public void cleanupSession(String callId) {
SipDelegate d = mDelegate;
+ if (d == null) {
+ return;
+ }
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> d.cleanupSession(callId));
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index b0e53e9..c3a4769 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -74,7 +74,7 @@
* Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
* and remains visible at the end
*/
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerVisibilityChanges() {
testSpec.assertLayers {
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
index c94152d..68bc1f69 100644
--- a/tests/TrustTests/AndroidManifest.xml
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -23,7 +23,9 @@
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
<uses-permission android:name="android.permission.TRUST_LISTENER" />
diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
index 83fc28f..8f200a6 100644
--- a/tests/TrustTests/src/android/trust/test/LockUserTest.kt
+++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
@@ -25,7 +25,6 @@
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
@@ -49,7 +48,6 @@
.around(lockStateTrackingRule)
.around(trustAgentRule)
- @Ignore("Causes issues with subsequent tests") // TODO: Enable test
@Test
fun lockUser_locksTheDevice() {
Log.i(TAG, "Locking user")
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
index bc100ba..006525d 100644
--- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -20,6 +20,8 @@
import android.util.Log
import android.view.WindowManagerGlobal
import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.google.common.truth.Truth.assertWithMessage
@@ -32,6 +34,7 @@
*/
class ScreenLockRule : TestRule {
private val context: Context = getApplicationContext()
+ private val uiDevice = UiDevice.getInstance(getInstrumentation())
private val windowManager = WindowManagerGlobal.getWindowManagerService()
private val lockPatternUtils = LockPatternUtils(context)
private var instantLockSavedValue = false
@@ -48,19 +51,21 @@
} finally {
removeScreenLock()
revertLockOnPowerButton()
+ verifyKeyguardDismissed()
}
}
}
private fun verifyNoScreenLockAlreadySet() {
assertWithMessage("Screen Lock must not already be set on device")
- .that(lockPatternUtils.isSecure(context.userId))
- .isFalse()
+ .that(lockPatternUtils.isSecure(context.userId))
+ .isFalse()
}
private fun verifyKeyguardDismissed() {
val maxWaits = 30
var waitCount = 0
+
while (windowManager.isKeyguardLocked && waitCount < maxWaits) {
Log.i(TAG, "Keyguard still showing; attempting to dismiss and wait 50ms ($waitCount)")
windowManager.dismissKeyguard(null, null)
@@ -68,19 +73,19 @@
waitCount++
}
assertWithMessage("Keyguard should be unlocked")
- .that(windowManager.isKeyguardLocked)
- .isFalse()
+ .that(windowManager.isKeyguardLocked)
+ .isFalse()
}
private fun setScreenLock() {
lockPatternUtils.setLockCredential(
- LockscreenCredential.createPin(PIN),
- LockscreenCredential.createNone(),
- context.userId
+ LockscreenCredential.createPin(PIN),
+ LockscreenCredential.createNone(),
+ context.userId
)
assertWithMessage("Screen Lock should now be set")
- .that(lockPatternUtils.isSecure(context.userId))
- .isTrue()
+ .that(lockPatternUtils.isSecure(context.userId))
+ .isTrue()
Log.i(TAG, "Device PIN set to $PIN")
}
@@ -90,14 +95,25 @@
}
private fun removeScreenLock() {
- lockPatternUtils.setLockCredential(
- LockscreenCredential.createNone(),
- LockscreenCredential.createPin(PIN),
- context.userId
- )
- Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard")
- Thread.sleep(50)
- windowManager.dismissKeyguard(null, null)
+ var lockCredentialUnset = lockPatternUtils.setLockCredential(
+ LockscreenCredential.createNone(),
+ LockscreenCredential.createPin(PIN),
+ context.userId)
+ Thread.sleep(100)
+ assertWithMessage("Lock screen credential should be unset")
+ .that(lockCredentialUnset)
+ .isTrue()
+
+ lockPatternUtils.setLockScreenDisabled(true, context.userId)
+ Thread.sleep(100)
+ assertWithMessage("Lockscreen needs to be disabled")
+ .that(lockPatternUtils.isLockScreenDisabled(context.userId))
+ .isTrue()
+
+ // this is here because somehow it helps the keyguard not get stuck
+ uiDevice.sleep()
+ Thread.sleep(500) // delay added to avoid initiating camera by double clicking power
+ uiDevice.wakeUp()
}
private fun revertLockOnPowerButton() {
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
index 1aec9b8..2e60f64 100644
--- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -21,8 +21,6 @@
import com.android.codegen.CANONICAL_BUILDER_CLASS
import com.android.codegen.CODEGEN_NAME
import com.android.codegen.CODEGEN_VERSION
-import com.sun.tools.javac.code.Symbol
-import com.sun.tools.javac.code.Type
import java.io.File
import java.io.FileNotFoundException
import javax.annotation.processing.AbstractProcessor
@@ -33,6 +31,7 @@
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
+import javax.lang.model.type.ExecutableType
import javax.tools.Diagnostic
private const val STALE_FILE_THRESHOLD_MS = 1000
@@ -102,14 +101,13 @@
append(" ")
append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) }))
append(" ")
- if (elem is Symbol) {
- if (elem.type is Type.MethodType) {
- append((elem.type as Type.MethodType).returnType)
- } else {
- append(elem.type)
- }
- append(" ")
+ val type = elem.asType()
+ if (type is ExecutableType) {
+ append(type.returnType)
+ } else {
+ append(type)
}
+ append(" ")
append(elem)
}
}
@@ -234,4 +232,4 @@
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latest()
}
-}
\ No newline at end of file
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 8630ec4..d85a5bd 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -967,16 +967,16 @@
*
* @param ifaceName Name of the interface.
*/
- public int getMaxNumScanSsids(@NonNull String ifaceName) {
+ public int getMaxSsidsPerScan(@NonNull String ifaceName) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
return 0;
}
try {
- return scannerImpl.getMaxNumScanSsids();
+ return scannerImpl.getMaxSsidsPerScan();
} catch (RemoteException e1) {
- Log.e(TAG, "Failed to getMaxNumScanSsids");
+ Log.e(TAG, "Failed to getMaxSsidsPerScan");
}
return 0;
}