Merge "Null check overlayPaths/resourceDirs when applying widget changes"
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b1f2d0..ddcc746 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -216,4 +216,11 @@
void dumpState(String[] args, PrintWriter pw);
boolean isAppIdleEnabled();
+
+ /**
+ * Returns the duration (in millis) for the window where events occurring will be
+ * considered as broadcast response, starting from the point when an app receives
+ * a broadcast.
+ */
+ long getBroadcastResponseWindowDurationMs();
}
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 8b26397..050e3df 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -347,6 +347,14 @@
*/
boolean mLinkCrossProfileApps =
ConstantsObserver.DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS;
+
+ /**
+ * Duration (in millis) for the window where events occurring will be considered as
+ * broadcast response, starting from the point when an app receives a broadcast.
+ */
+ volatile long mBroadcastResponseWindowDurationMillis =
+ ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS;
+
/**
* Whether we should allow apps into the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not.
@@ -1774,6 +1782,10 @@
}
}
+ @Override
+ public long getBroadcastResponseWindowDurationMs() {
+ return mBroadcastResponseWindowDurationMillis;
+ }
@Override
public void flushToDisk() {
@@ -2042,6 +2054,10 @@
TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
pw.println();
+ pw.print(" mBroadcastResponseWindowDurationMillis=");
+ TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw);
+ pw.println();
+
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
pw.print(" mAllowRestrictedBucket=");
@@ -2473,6 +2489,8 @@
KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "rare",
KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "restricted"
};
+ private static final String KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+ "broadcast_response_window_timeout_ms";
public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
@@ -2502,6 +2520,8 @@
public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
+ public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+ 2 * ONE_MINUTE;
ConstantsObserver(Handler handler) {
super(handler);
@@ -2619,6 +2639,11 @@
KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION,
DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT);
break;
+ case KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS:
+ mBroadcastResponseWindowDurationMillis = properties.getLong(
+ KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS,
+ DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS);
+ break;
default:
if (!timeThresholdsUpdated
&& (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD)
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index c5dc51c..e407e31 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -5,7 +5,9 @@
"options": [
{"include-filter": "android.app.usage.cts.UsageStatsTest"},
{"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.MediumTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
diff --git a/api/api.go b/api/api.go
index aa9e399e..17649e8 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,7 +27,6 @@
const art = "art.module.public.api"
const conscrypt = "conscrypt.module.public.api"
const i18n = "i18n.module.public.api"
-var modules_with_only_public_scope = []string{i18n, conscrypt}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -149,8 +148,6 @@
// This produces the same annotations.zip as framework-doc-stubs, but by using
// outputs from individual modules instead of all the source code.
func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
- // Conscrypt and i18n currently do not enable annotations
- modules = removeAll(modules, []string{conscrypt, i18n})
props := genruleProps{}
props.Name = proptools.StringPtr("sdk-annotations.zip")
props.Tools = []string{"merge_annotation_zips", "soong_zip"}
@@ -195,11 +192,8 @@
func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
props := libraryProps{}
- modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope)
props.Name = proptools.StringPtr("all-modules-system-stubs")
- props.Static_libs = append(
- transformArray(modules_with_only_public_scope, "", ".stubs"),
- transformArray(modules_with_system_stubs, "", ".stubs.system")...)
+ props.Static_libs = transformArray(modules, "", ".stubs.system")
props.Sdk_version = proptools.StringPtr("module_current")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(java.LibraryFactory, &props)
@@ -226,8 +220,6 @@
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
var textFiles []MergedTxtDefinition
- // Two module libraries currently do not support @SystemApi so only have the public scope.
- bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope)
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
for i, f := range []string{"current.txt", "removed.txt"} {
@@ -241,14 +233,14 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-system-" + f,
- Modules: bcpWithSystemApi,
+ Modules: bootclasspath,
ModuleTag: "{.system" + tagSuffix[i],
Scope: "system",
})
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-module-lib-" + f,
- Modules: bcpWithSystemApi,
+ Modules: bootclasspath,
ModuleTag: "{.module-lib" + tagSuffix[i],
Scope: "module-lib",
})
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index dd33fdf..c8d2e0e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -280,7 +280,7 @@
// Lower privacy policy (less restrictive) wins.
report->set_privacy_policy(args.getPrivacyPolicy());
}
- report->set_all_sections(report->all_sections() | args.all());
+ report->set_all_sections(report->all_sections() || args.all());
for (int section: args.sections()) {
if (!has_section(*report, section)) {
report->add_section(section);
diff --git a/core/api/current.txt b/core/api/current.txt
index 258acf9..922ad16 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4183,6 +4183,7 @@
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
method public void overridePendingTransition(int, int);
+ method public void overridePendingTransition(int, int, int);
method public void postponeEnterTransition();
method public void recreate();
method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
@@ -4486,6 +4487,7 @@
method public static android.app.ActivityOptions makeBasic();
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+ method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int);
method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
method @java.lang.SafeVarargs public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View,java.lang.String>...);
@@ -31681,6 +31683,7 @@
method public boolean isDeviceLightIdleMode();
method public boolean isIgnoringBatteryOptimizations(String);
method public boolean isInteractive();
+ method public boolean isLowPowerStandbyEnabled();
method public boolean isPowerSaveMode();
method public boolean isRebootingUserspaceSupported();
method @Deprecated public boolean isScreenOn();
@@ -31692,6 +31695,7 @@
field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+ field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -37928,21 +37932,23 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(@NonNull android.widget.RemoteViews);
+ ctor @Deprecated public Dataset.Builder(@NonNull android.widget.RemoteViews);
+ ctor public Dataset.Builder(@NonNull android.service.autofill.Presentations);
ctor public Dataset.Builder();
method @NonNull public android.service.autofill.Dataset build();
method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender);
+ method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull android.view.autofill.AutofillId, @Nullable android.service.autofill.Field);
method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String);
- method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
}
public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -37959,6 +37965,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.DateValueSanitizer> CREATOR;
}
+ public final class Field {
+ method @Nullable public android.service.autofill.Presentations getPresentations();
+ method @Nullable public android.view.autofill.AutofillValue getValue();
+ }
+
+ public static final class Field.Builder {
+ ctor public Field.Builder();
+ method @NonNull public android.service.autofill.Field build();
+ method @NonNull public android.service.autofill.Field.Builder setFilter(@Nullable java.util.regex.Pattern);
+ method @NonNull public android.service.autofill.Field.Builder setPresentations(@NonNull android.service.autofill.Presentations);
+ method @NonNull public android.service.autofill.Field.Builder setValue(@NonNull android.view.autofill.AutofillValue);
+ }
+
public final class FieldClassification {
method @NonNull public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
}
@@ -38041,11 +38060,14 @@
method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
method @NonNull public android.service.autofill.FillResponse build();
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
- method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
- method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
+ method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.service.autofill.Presentations);
method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
+ method @NonNull public android.service.autofill.FillResponse.Builder setDialogHeader(@NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
+ method @NonNull public android.service.autofill.FillResponse.Builder setFillDialogTriggerIds(@NonNull android.view.autofill.AutofillId...);
method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews);
@@ -38090,6 +38112,22 @@
public interface OnClickAction {
}
+ public final class Presentations {
+ method @Nullable public android.widget.RemoteViews getDialogPresentation();
+ method @Nullable public android.service.autofill.InlinePresentation getInlinePresentation();
+ method @Nullable public android.service.autofill.InlinePresentation getInlineTooltipPresentation();
+ method @Nullable public android.widget.RemoteViews getMenuPresentation();
+ }
+
+ public static final class Presentations.Builder {
+ ctor public Presentations.Builder();
+ method @NonNull public android.service.autofill.Presentations build();
+ method @NonNull public android.service.autofill.Presentations.Builder setDialogPresentation(@NonNull android.widget.RemoteViews);
+ method @NonNull public android.service.autofill.Presentations.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Presentations.Builder setInlineTooltipPresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Presentations.Builder setMenuPresentation(@NonNull android.widget.RemoteViews);
+ }
+
public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
ctor public RegexValidator(@NonNull android.view.autofill.AutofillId, @NonNull java.util.regex.Pattern);
method public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 28f9ab4..8e68858 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -72,6 +72,7 @@
field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+ field public static final String BIND_TRACE_REPORT_SERVICE = "android.permission.BIND_TRACE_REPORT_SERVICE";
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
@@ -167,6 +168,7 @@
field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+ field public static final String MANAGE_LOW_POWER_STANDBY = "android.permission.MANAGE_LOW_POWER_STANDBY";
field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
@@ -2608,6 +2610,8 @@
public final class VirtualDeviceParams implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public java.util.Set<android.content.ComponentName> getAllowedActivities();
+ method @Nullable public java.util.Set<android.content.ComponentName> getBlockedActivities();
method public int getLockState();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -2619,6 +2623,8 @@
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@Nullable java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@Nullable java.util.Set<android.content.ComponentName>);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
}
@@ -9313,11 +9319,14 @@
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable();
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed();
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public boolean isLowPowerStandbySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyActiveDuringMaintenance(boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
@@ -10638,9 +10647,9 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+ ctor @Deprecated public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
method @NonNull public android.service.autofill.Dataset.Builder setContent(@NonNull android.view.autofill.AutofillId, @Nullable android.content.ClipData);
- method @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
}
public abstract class InlineSuggestionRenderService extends android.app.Service {
@@ -11192,7 +11201,7 @@
method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
method public long getMaximumDataBlockSize();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
- method @NonNull public String getPersistentDataPackageName();
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
method public byte[] read();
method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
@@ -11430,6 +11439,22 @@
}
+package android.service.tracing {
+
+ public class TraceReportService extends android.app.Service {
+ ctor public TraceReportService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public boolean onMessage(@NonNull android.os.Message);
+ method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
+ }
+
+ public static final class TraceReportService.TraceParams {
+ method @NonNull public android.os.ParcelFileDescriptor getFd();
+ method @NonNull public java.util.UUID getUuid();
+ }
+
+}
+
package android.service.translation {
public abstract class TranslationService extends android.app.Service {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0bdbd10..c39394b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2,6 +2,7 @@
package android {
public static final class Manifest.permission {
+ field public static final String ACCESS_KEYGUARD_SECURE_STORAGE = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE";
field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
@@ -105,6 +106,7 @@
@UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
method public final boolean addDumpable(@NonNull android.util.Dumpable);
+ method public void dumpInternal(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
method public void onMovedToDisplay(int, android.content.res.Configuration);
}
@@ -152,7 +154,7 @@
public class ActivityOptions {
method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
- method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
+ method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
@@ -286,8 +288,8 @@
}
public class KeyguardManager {
- method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean checkLock(int, @Nullable byte[]);
- method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
+ method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE}) public boolean checkLock(int, @Nullable byte[]);
+ method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
}
public class LocaleManager {
@@ -461,6 +463,7 @@
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
method public void forceUpdateUserSetupComplete(int);
method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
+ method public int getDeviceOwnerType(@NonNull android.content.ComponentName);
method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
method public long getLastBugReportRequestTime();
method public long getLastNetworkLogRetrievalTime();
@@ -476,6 +479,7 @@
method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+ method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
@@ -581,6 +585,15 @@
}
+package android.app.trust {
+
+ public class TrustManager {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) public void enableTrustAgentForUserForTest(@NonNull android.content.ComponentName, int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) public void reportUserRequestedUnlock(int);
+ }
+
+}
+
package android.app.usage {
public class NetworkStatsManager {
@@ -1718,7 +1731,9 @@
}
public final class PowerManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
}
public class Process {
@@ -2359,6 +2374,14 @@
}
+package android.service.trust {
+
+ public class TrustAgentService extends android.app.Service {
+ method public void onUserRequestedUnlock();
+ }
+
+}
+
package android.service.voice {
public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a2259f3..8234f03 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -38,6 +38,11 @@
srcs: ["android/tracing/ITracingServiceProxy.aidl"],
}
+filegroup {
+ name: "TraceReportParams.aidl",
+ srcs: ["android/tracing/TraceReportParams.aidl"],
+}
+
// These are subset of framework-core-sources that are needed by the
// android.test.mock library. The implementation of android.test.mock references
// private members of various components to allow mocking of classes that cannot
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 93d1e03..530666b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -33,12 +33,16 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.VoiceInteractor.Request;
import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -788,6 +792,16 @@
private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+ /**
+ * After {@link Build.VERSION_CODES#TIRAMISU},
+ * {@link #dump(String, FileDescriptor, PrintWriter, String[])} is not called if
+ * {@code dumpsys activity} is called with some special arguments.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @VisibleForTesting
+ private static final long DUMP_IGNORES_SPECIAL_ARGS = 149254050L;
+
private static class ManagedDialog {
Dialog mDialog;
Bundle mArgs;
@@ -6131,8 +6145,31 @@
* the outgoing activity. Use 0 for no animation.
*/
public void overridePendingTransition(int enterAnim, int exitAnim) {
- ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(),
- enterAnim, exitAnim);
+ overridePendingTransition(enterAnim, exitAnim, 0);
+ }
+
+ /**
+ * Call immediately after one of the flavors of {@link #startActivity(Intent)}
+ * or {@link #finish} to specify an explicit transition animation to
+ * perform next.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+ * to using this with starting activities is to supply the desired animation
+ * information through a {@link ActivityOptions} bundle to
+ * {@link #startActivity(Intent, Bundle)} or a related function. This allows
+ * you to specify a custom animation even when starting an activity from
+ * outside the context of the current top activity.
+ *
+ * @param enterAnim A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation if
+ * the animation requires a background. Set to 0 to not override the default color.
+ */
+ public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) {
+ ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim,
+ exitAnim, backgroundColor);
}
/**
@@ -7079,7 +7116,18 @@
/**
* Print the Activity's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity <activity_component_name>".
+ * you run <code>adb shell dumpsys activity <activity_component_name></code>.
+ *
+ * <p>This method won't be called if the app targets
+ * {@link android.os.Build.VERSION_CODES#TIRAMISU} or later if the dump request starts with one
+ * of the following arguments:
+ * <ul>
+ * <li>--autofill
+ * <li>--contentcapture
+ * <li>--translation
+ * <li>--list-dumpables
+ * <li>--dump-dumpable
+ * </ul>
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
@@ -7106,11 +7154,20 @@
return mDumpableContainer.addDumpable(dumpable);
}
- void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+ /**
+ * This is the real method called by {@code ActivityThread}, but it's also exposed so
+ * CTS can test for the special args cases.
+ *
+ * @hide
+ */
+ @TestApi
+ @VisibleForTesting
+ @SuppressLint("OnNameExpected")
+ public void dumpInternal(@NonNull String prefix,
+ @SuppressLint("UseParcelFileDescriptor") @Nullable FileDescriptor fd,
@NonNull PrintWriter writer, @Nullable String[] args) {
- String innerPrefix = prefix + " ";
-
- if (args != null && args.length > 0) {
+ if (args != null && args.length > 0
+ && CompatChanges.isChangeEnabled(DUMP_IGNORES_SPECIAL_ARGS)) {
// Handle special cases
switch (args[0]) {
case "--autofill":
@@ -7145,6 +7202,12 @@
return;
}
}
+ dump(prefix, fd, writer, args);
+ }
+
+ void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ String innerPrefix = prefix + " ";
writer.print(prefix); writer.print("Local Activity ");
writer.print(Integer.toHexString(System.identityHashCode(this)));
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index eb4a355..605a1fa 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -428,11 +428,11 @@
}
}
- void overridePendingTransition(IBinder token, String packageName,
- int enterAnim, int exitAnim) {
+ void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim,
+ int backgroundColor) {
try {
getActivityClientController().overridePendingTransition(token, packageName,
- enterAnim, exitAnim);
+ enterAnim, exitAnim, backgroundColor);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5e5649f..e405b60 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -126,6 +126,12 @@
public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes";
/**
+ * Custom background color for animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_BACKGROUND_COLOR = "android:activity.backgroundColor";
+
+ /**
* Bitmap for thumbnail animation.
* @hide
*/
@@ -389,6 +395,7 @@
private int mCustomEnterResId;
private int mCustomExitResId;
private int mCustomInPlaceResId;
+ private int mCustomBackgroundColor;
private Bitmap mThumbnail;
private int mStartX;
private int mStartY;
@@ -453,7 +460,27 @@
*/
public static ActivityOptions makeCustomAnimation(Context context,
int enterResId, int exitResId) {
- return makeCustomAnimation(context, enterResId, exitResId, null, null, null);
+ return makeCustomAnimation(context, enterResId, exitResId, 0, null, null);
+ }
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation if
+ * the animation requires a background. Set to 0 to not override the default color.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
+ int enterResId, int exitResId, int backgroundColor) {
+ return makeCustomAnimation(context, enterResId, exitResId, backgroundColor, null, null);
}
/**
@@ -477,12 +504,14 @@
*/
@UnsupportedAppUsage
public static ActivityOptions makeCustomAnimation(Context context,
- int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
+ int enterResId, int exitResId, int backgroundColor, Handler handler,
+ OnAnimationStartedListener listener) {
ActivityOptions opts = new ActivityOptions();
opts.mPackageName = context.getPackageName();
opts.mAnimationType = ANIM_CUSTOM;
opts.mCustomEnterResId = enterResId;
opts.mCustomExitResId = exitResId;
+ opts.mCustomBackgroundColor = backgroundColor;
opts.setOnAnimationStartedListener(handler, listener);
return opts;
}
@@ -510,11 +539,11 @@
*/
@TestApi
public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
- int enterResId, int exitResId, @Nullable Handler handler,
+ int enterResId, int exitResId, int backgroundColor, @Nullable Handler handler,
@Nullable OnAnimationStartedListener startedListener,
@Nullable OnAnimationFinishedListener finishedListener) {
- ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
- startedListener);
+ ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, backgroundColor,
+ handler, startedListener);
opts.setOnAnimationFinishedListener(handler, finishedListener);
return opts;
}
@@ -547,8 +576,8 @@
int enterResId, int exitResId, @Nullable Handler handler,
@Nullable OnAnimationStartedListener startedListener,
@Nullable OnAnimationFinishedListener finishedListener) {
- ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
- startedListener, finishedListener);
+ ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, 0,
+ handler, startedListener, finishedListener);
opts.mOverrideTaskTransition = true;
return opts;
}
@@ -1243,6 +1272,11 @@
return mCustomInPlaceResId;
}
+ /** @hide */
+ public int getCustomBackgroundColor() {
+ return mCustomBackgroundColor;
+ }
+
/**
* The thumbnail is copied into a hardware bitmap when it is bundled and sent to the system, so
* it should always be backed by a HardwareBuffer on the other end.
@@ -1775,6 +1809,7 @@
case ANIM_CUSTOM:
mCustomEnterResId = otherOptions.mCustomEnterResId;
mCustomExitResId = otherOptions.mCustomExitResId;
+ mCustomBackgroundColor = otherOptions.mCustomBackgroundColor;
mThumbnail = null;
if (mAnimationStartedListener != null) {
try {
@@ -1862,6 +1897,7 @@
case ANIM_CUSTOM:
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+ b.putInt(KEY_ANIM_BACKGROUND_COLOR, mCustomBackgroundColor);
b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3825d8d..876e401 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -200,6 +200,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SomeArgs;
@@ -4591,7 +4592,7 @@
if (r != null && r.activity != null) {
PrintWriter pw = new FastPrintWriter(new FileOutputStream(
info.fd.getFileDescriptor()));
- r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
+ r.activity.dumpInternal(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
pw.flush();
}
} finally {
@@ -7812,6 +7813,8 @@
MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
+ BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
+ BinderCallsStats.startForBluetooth(context); });
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0081234..0d1bc05 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1327,9 +1327,17 @@
*/
public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
+ /**
+ * Access restricted settings.
+ *
+ * @hide
+ */
+ public static final int OP_ACCESS_RESTRICTED_SETTINGS =
+ AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 119;
+ public static final int _NUM_OP = 120;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1784,6 +1792,14 @@
@SystemApi
public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
+ /**
+ * Limit user accessing restricted settings.
+ *
+ * @hide
+ */
+ public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
+ "android:access_restricted_settings";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2004,6 +2020,7 @@
OP_NEARBY_WIFI_DEVICES, // OP_NEARBY_WIFI_DEVICES
OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE
OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER
+ OP_ACCESS_RESTRICTED_SETTINGS, // OP_ACCESS_RESTRICTED_SETTINGS
};
/**
@@ -2129,6 +2146,7 @@
OPSTR_NEARBY_WIFI_DEVICES,
OPSTR_ESTABLISH_VPN_SERVICE,
OPSTR_ESTABLISH_VPN_MANAGER,
+ OPSTR_ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2255,6 +2273,7 @@
"NEARBY_WIFI_DEVICES",
"ESTABLISH_VPN_SERVICE",
"ESTABLISH_VPN_MANAGER",
+ "ACCESS_RESTRICTED_SETTINGS",
};
/**
@@ -2382,6 +2401,7 @@
Manifest.permission.NEARBY_WIFI_DEVICES,
null, // no permission for OP_ESTABLISH_VPN_SERVICE
null, // no permission for OP_ESTABLISH_VPN_MANAGER
+ null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2509,6 +2529,7 @@
null, // NEARBY_WIFI_DEVICES
null, // ESTABLISH_VPN_SERVICE
null, // ESTABLISH_VPN_MANAGER
+ null, // ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2635,6 +2656,7 @@
null, // NEARBY_WIFI_DEVICES
null, // ESTABLISH_VPN_SERVICE
null, // ESTABLISH_VPN_MANAGER
+ null, // ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2760,6 +2782,7 @@
AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES
AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE
AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER
+ AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2889,6 +2912,7 @@
false, // NEARBY_WIFI_DEVICES
false, // OP_ESTABLISH_VPN_SERVICE
false, // OP_ESTABLISH_VPN_MANAGER
+ true, // ACCESS_RESTRICTED_SETTINGS
};
/**
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 83c57c5..396e552 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -112,7 +112,7 @@
* calls, so this method should be the same as them to keep the invocation order.
*/
void overridePendingTransition(in IBinder token, in String packageName,
- int enterAnim, int exitAnim);
+ int enterAnim, int exitAnim, int backgroundColor);
int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
/** See {@link android.app.Activity#setDisablePreviewScreenshots}. */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index dd832c8..bbdd705 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1034,15 +1034,14 @@
}});
registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
- new CachedServiceFetcher<PersistentDataBlockManager>() {
+ new StaticServiceFetcher<PersistentDataBlockManager>() {
@Override
- public PersistentDataBlockManager createService(ContextImpl ctx)
- throws ServiceNotFoundException {
+ public PersistentDataBlockManager createService() throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE);
IPersistentDataBlockService persistentDataBlockService =
IPersistentDataBlockService.Stub.asInterface(b);
if (persistentDataBlockService != null) {
- return new PersistentDataBlockManager(ctx, persistentDataBlockService);
+ return new PersistentDataBlockManager(persistentDataBlockService);
} else {
// not supported
return null;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3960f4e..9ac4030 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -454,6 +454,52 @@
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* </ul>
*
+ * <p>Once the device admin app is set as the device owner, the following APIs are available for
+ * managing polices on the device:
+ * <ul>
+ * <li>{@link #isDeviceManaged()}</li>
+ * <li>{@link #isUninstallBlocked(ComponentName, String)}</li>
+ * <li>{@link #setUninstallBlocked(ComponentName, String, boolean)}</li>
+ * <li>{@link #setUserControlDisabledPackages(ComponentName, List)}</li>
+ * <li>{@link #getUserControlDisabledPackages(ComponentName)}</li>
+ * <li>{@link #setOrganizationName(ComponentName, CharSequence)}</li>
+ * <li>{@link #setShortSupportMessage(ComponentName, CharSequence)}</li>
+ * <li>{@link #isBackupServiceEnabled(ComponentName)}</li>
+ * <li>{@link #setBackupServiceEnabled(ComponentName, boolean)}</li>
+ * <li>{@link #isLockTaskPermitted(String)}</li>
+ * <li>{@link #setLockTaskFeatures(ComponentName, int)}, where the following lock task features
+ * can be set (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link #LOCK_TASK_FEATURE_SYSTEM_INFO}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_KEYGUARD}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+ * </ul>
+ * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
+ * <li>{@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}</li>
+ * <li>{@link #clearPackagePersistentPreferredActivities(ComponentName, String)} </li>
+ * <li>{@link #wipeData(int)}</li>
+ * <li>{@link #isDeviceOwnerApp(String)}</li>
+ * <li>{@link #clearDeviceOwnerApp(String)}</li>
+ * <li>{@link #setPermissionGrantState(ComponentName, String, String, int)}, where
+ * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be
+ * {@link #PERMISSION_GRANT_STATE_GRANTED}, {@link #PERMISSION_GRANT_STATE_DENIED}, or
+ * {@link #PERMISSION_GRANT_STATE_DEFAULT} and can <b>only</b> be applied to the device admin
+ * app (otherwise a {@link SecurityException} will be thrown)</li>
+ * <li>{@link #addUserRestriction(ComponentName, String)}, where the following user restrictions
+ * are permitted (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link UserManager#DISALLOW_ADD_USER}</li>
+ * <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li>
+ * <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li>
+ * <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li>
+ * <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li>
+ * <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li>
+ * </ul>
+ * <li>{@link #clearUserRestriction(ComponentName, String)}</li>
+ * </ul>
+ *
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -14577,6 +14623,7 @@
*
* @hide
*/
+ @TestApi
public void setDeviceOwnerType(@NonNull ComponentName admin,
@DeviceOwnerType int deviceOwnerType) {
throwIfParentInstance("setDeviceOwnerType");
@@ -14600,6 +14647,7 @@
*
* @hide
*/
+ @TestApi
@DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) {
throwIfParentInstance("getDeviceOwnerType");
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index edabccf..7956a35 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -17,6 +17,7 @@
package android.app.trust;
import android.app.trust.ITrustListener;
+import android.content.ComponentName;
import android.hardware.biometrics.BiometricSourceType;
/**
@@ -29,6 +30,7 @@
void reportUserRequestedUnlock(int userId);
void reportUnlockLockout(int timeoutMs, int userId);
void reportEnabledTrustAgentsChanged(int userId);
+ void enableTrustAgentForUserForTest(in ComponentName componentName, int userId);
void registerTrustListener(in ITrustListener trustListener);
void unregisterTrustListener(in ITrustListener trustListener);
void reportKeyguardShowingChanged();
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 70b7de0..fba2d3e 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -16,10 +16,14 @@
package android.app.trust;
-import android.Manifest;
+import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
@@ -33,9 +37,17 @@
import java.util.List;
/**
- * See {@link com.android.server.trust.TrustManagerService}
+ * Interface to the system service managing trust.
+ *
+ * <p>This class is for internal use only. This class is marked {@code @TestApi} to
+ * enable testing the trust system including {@link android.service.trust.TrustAgentService}.
+ * Methods which are currently not used in tests are marked @hide.
+ *
+ * @see com.android.server.trust.TrustManagerService
+ *
* @hide
*/
+@TestApi
@SystemService(Context.TRUST_SERVICE)
public class TrustManager {
@@ -51,7 +63,8 @@
private final ITrustManager mService;
private final ArrayMap<TrustListener, ITrustListener> mTrustListeners;
- public TrustManager(IBinder b) {
+ /** @hide */
+ public TrustManager(@NonNull IBinder b) {
mService = ITrustManager.Stub.asInterface(b);
mTrustListeners = new ArrayMap<TrustListener, ITrustListener>();
}
@@ -62,8 +75,10 @@
*
* @param userId The id for the user to be locked/unlocked.
* @param locked The value for that user's locked state.
+ *
+ * @hide
*/
- @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void setDeviceLockedForUser(int userId, boolean locked) {
try {
mService.setDeviceLockedForUser(userId, locked);
@@ -78,8 +93,11 @@
* @param successful if true, the unlock attempt was successful.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void reportUnlockAttempt(boolean successful, int userId) {
try {
mService.reportUnlockAttempt(successful, userId);
@@ -93,6 +111,7 @@
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
*/
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void reportUserRequestedUnlock(int userId) {
try {
mService.reportUserRequestedUnlock(userId);
@@ -112,7 +131,10 @@
* attempt to unlock the device again.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @hide
*/
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void reportUnlockLockout(int timeoutMs, int userId) {
try {
mService.reportUnlockLockout(timeoutMs, userId);
@@ -125,7 +147,10 @@
* Reports that the list of enabled trust agents changed for user {@param userId}.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @hide
*/
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void reportEnabledTrustAgentsChanged(int userId) {
try {
mService.reportEnabledTrustAgentsChanged(userId);
@@ -135,10 +160,33 @@
}
/**
+ * Enables a trust agent.
+ *
+ * <p>The agent is specified by {@code componentName} and must be a subclass of
+ * {@link android.service.trust.TrustAgentService} and otherwise meet the requirements
+ * to be a trust agent.
+ *
+ * <p>This method can only be used in tests.
+ *
+ * @param componentName the trust agent to enable
+ */
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
+ public void enableTrustAgentForUserForTest(@NonNull ComponentName componentName, int userId) {
+ try {
+ mService.enableTrustAgentForUserForTest(componentName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Reports that the visibility of the keyguard has changed.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @hide
*/
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void reportKeyguardShowingChanged() {
try {
mService.reportKeyguardShowingChanged();
@@ -151,7 +199,10 @@
* Registers a listener for trust events.
*
* Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
+ *
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.TRUST_LISTENER)
public void registerTrustListener(final TrustListener trustListener) {
try {
ITrustListener.Stub iTrustListener = new ITrustListener.Stub() {
@@ -192,7 +243,10 @@
* Unregisters a listener for trust events.
*
* Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
+ *
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.TRUST_LISTENER)
public void unregisterTrustListener(final TrustListener trustListener) {
ITrustListener iTrustListener = mTrustListeners.remove(trustListener);
if (iTrustListener != null) {
@@ -207,6 +261,8 @@
/**
* @return whether {@param userId} has enabled and configured trust agents. Ignores short-term
* unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}.
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.TRUST_LISTENER)
public boolean isTrustUsuallyManaged(int userId) {
@@ -223,8 +279,10 @@
* can be skipped.
*
* @param userId
+ *
+ * @hide
*/
- @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void unlockedByBiometricForUser(int userId, BiometricSourceType source) {
try {
mService.unlockedByBiometricForUser(userId, source);
@@ -235,8 +293,10 @@
/**
* Clears authentication by the specified biometric type for all users.
+ *
+ * @hide
*/
- @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+ @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE)
public void clearAllBiometricRecognized(BiometricSourceType source, int unlockedUser) {
try {
mService.clearAllBiometricRecognized(source, unlockedUser);
@@ -264,6 +324,7 @@
}
};
+ /** @hide */
public interface TrustListener {
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 2ddfeb4..1d0f7c0 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.os.Parcel;
@@ -106,11 +107,13 @@
}
/**
- * Returns the set of activities allowed to be streamed, or {@code null} if this is not set.
+ * Returns the set of activities allowed to be streamed, or {@code null} if all activities are
+ * allowed, except the ones explicitly blocked.
*
* @see Builder#setAllowedActivities(Set)
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Null and empty have different semantics - Null allows all activities to be streamed
+ @SuppressLint("NullableCollection")
@Nullable
public Set<ComponentName> getAllowedActivities() {
if (mAllowedActivities == null) {
@@ -120,12 +123,13 @@
}
/**
- * Returns the set of activities that are blocked from streaming, or {@code null} if this is not
- * set.
+ * Returns the set of activities that are blocked from streaming, or {@code null} to indicate
+ * that all activities in {@link #getAllowedActivities} are allowed.
*
* @see Builder#setBlockedActivities(Set)
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+ @SuppressLint("NullableCollection")
@Nullable
public Set<ComponentName> getBlockedActivities() {
if (mBlockedActivities == null) {
@@ -255,8 +259,10 @@
*
* @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
* in the virtual device.
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Null and empty have different semantics - Null allows all activities to be streamed
+ @SuppressLint("NullableCollection")
+ @NonNull
public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) {
if (mBlockedActivities != null && allowedActivities != null) {
throw new IllegalArgumentException(
@@ -279,8 +285,10 @@
*
* @param blockedActivities A set of {@link ComponentName} to be blocked launching from
* virtual device.
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+ @SuppressLint("NullableCollection")
+ @NonNull
public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) {
if (mAllowedActivities != null && blockedActivities != null) {
throw new IllegalArgumentException(
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 0304815..27403ec 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -122,9 +122,9 @@
void removePortAssociation(in String inputPort);
// Add a runtime association between the input device and display.
- void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId);
+ void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
// Remove the runtime association between the input device and display.
- void removeUniqueIdAssociation(in String inputDeviceName);
+ void removeUniqueIdAssociation(in String inputPort);
InputSensorInfo[] getSensorList(int deviceId);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cbc8373..979e9dd 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1359,19 +1359,18 @@
}
/**
- * Add a runtime association between the input device name and display, by unique id. Input
- * device names are expected to be unique.
- * @param inputDeviceName The name of the input device.
+ * Add a runtime association between the input port and display, by unique id. Input ports are
+ * expected to be unique.
+ * @param inputPort The port of the input device.
* @param displayUniqueId The unique id of the associated display.
* <p>
* Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
- public void addUniqueIdAssociation(@NonNull String inputDeviceName,
- @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
try {
- mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId);
+ mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1379,15 +1378,15 @@
/**
* Removes a runtime association between the input device and display.
- * @param inputDeviceName The name of the input device.
+ * @param inputPort The port of the input device.
* <p>
* Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
- public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+ public void removeUniqueIdAssociation(@NonNull String inputPort) {
try {
- mIm.removeUniqueIdAssociation(inputDeviceName);
+ mIm.removeUniqueIdAssociation(inputPort);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 2484cf0..d572f5a 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -19,6 +19,7 @@
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import android.animation.ValueAnimator;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,6 +45,8 @@
import android.view.WindowInsets;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import java.util.Objects;
@@ -115,6 +118,12 @@
}
private static final class Impl implements Callback {
+ private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
+
+ // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
+ private static final Interpolator LEGACY_DECELERATE =
+ new PathInterpolator(0f, 0f, 0.2f, 1f);
+
@NonNull
private final InputMethodService mService;
@@ -133,6 +142,12 @@
@Appearance
private int mAppearance;
+ @FloatRange(from = 0.0f, to = 1.0f)
+ private float mDarkIntensity;
+
+ @Nullable
+ private ValueAnimator mTintAnimator;
+
Impl(@NonNull InputMethodService inputMethodService) {
mService = inputMethodService;
}
@@ -368,6 +383,10 @@
if (mDestroyed) {
return;
}
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ mTintAnimator = null;
+ }
if (mSystemOverlayChangedReceiver != null) {
mService.unregisterReceiver(mSystemOverlayChangedReceiver);
mSystemOverlayChangedReceiver = null;
@@ -416,10 +435,24 @@
}
final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
- setIconTintInternal(targetDarkIntensity);
+
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ }
+ mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+ mTintAnimator.addUpdateListener(
+ animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
+ mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
+ mTintAnimator.setStartDelay(0);
+ mTintAnimator.setInterpolator(LEGACY_DECELERATE);
+ mTintAnimator.start();
}
private void setIconTintInternal(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ if (mNavigationBarFrame == null) {
+ return;
+ }
final NavigationBarView navigationBarView =
mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
if (navigationBarView == null) {
@@ -439,6 +472,7 @@
return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ " mAppearance=0x" + Integer.toHexString(mAppearance)
+ + " mDarkIntensity=" + mDarkIntensity
+ "}";
}
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2d33817..07a5132 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -34,6 +34,7 @@
import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
import android.service.batterystats.BatteryStatsServiceDumpProto;
import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.text.format.DateFormat;
import android.util.ArrayMap;
@@ -2654,6 +2655,46 @@
*/
public abstract Timer getPhoneDataConnectionTimer(int dataType);
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_",
+ value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE,
+ RADIO_ACCESS_TECHNOLOGY_NR})
+ public @interface RadioAccessTechnology {
+ }
+
+ /** @hide */
+ public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"};
+
+ /**
+ * Returns the time in microseconds that the mobile radio has been active on a
+ * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+ * transmission power level.
+ *
+ * @param rat Radio Access Technology {@see RadioAccessTechnology}
+ * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+ * RADIO_ACCESS_TECHNOLOGY_NR. Use
+ * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+ * Technologies.
+ * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+ * @param elapsedRealtimeMs current elapsed realtime
+ * @return time (in milliseconds) the mobile radio spent active in the specified state,
+ * while on battery.
+ * @hide
+ */
+ public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs);
+
static final String[] WIFI_SUPPL_STATE_NAMES = {
"invalid", "disconn", "disabled", "inactive", "scanning",
"authenticating", "associating", "associated", "4-way-handshake",
@@ -3997,6 +4038,89 @@
}
}
+ private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix,
+ long rawRealtimeMs) {
+ final String allFrequenciesHeader =
+ " All frequencies:\n";
+ final String[] nrFrequencyRangeDescription = new String[]{
+ " Unknown frequency:\n",
+ " Low frequency (less than 1GHz):\n",
+ " Middle frequency (1GHz to 3GHz):\n",
+ " High frequency (3GHz to 6GHz):\n",
+ " Mmwave frequency (greater than 6GHz):\n"};
+ final String signalStrengthHeader =
+ " Signal Strength Time:\n";
+ final String[] signalStrengthDescription = new String[]{
+ " unknown: ",
+ " poor: ",
+ " moderate: ",
+ " good: ",
+ " great: "};
+
+ final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000,
+ STATS_SINCE_CHARGED) / 1000;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("Active Cellular Radio Access Technology Breakdown:");
+ pw.println(sb);
+
+ boolean hasData = false;
+ final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels();
+ for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(":\n");
+ sb.append(prefix);
+
+ final int numFreqLvl =
+ rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1;
+ for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) {
+ final int freqDescriptionStart = sb.length();
+ boolean hasFreqData = false;
+ if (rat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ sb.append(nrFrequencyRangeDescription[freqLvl]);
+ } else {
+ sb.append(allFrequenciesHeader);
+ }
+
+ sb.append(prefix);
+ sb.append(signalStrengthHeader);
+ for (int strength = 0; strength < numSignalStrength; strength++) {
+ final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength,
+ rawRealtimeMs);
+ if (timeMs <= 0) continue;
+ hasFreqData = true;
+ sb.append(prefix);
+ sb.append(signalStrengthDescription[strength]);
+ formatTimeMs(sb, timeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+ sb.append(")\n");
+ }
+
+ if (hasFreqData) {
+ hasData = true;
+ pw.print(sb);
+ sb.setLength(0);
+ sb.append(prefix);
+ } else {
+ // No useful data was printed, rewind sb to before the start of this frequency.
+ sb.setLength(freqDescriptionStart);
+ }
+ }
+ }
+
+ if (!hasData) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" (no activity)");
+ pw.println(sb);
+ }
+ }
+
/**
* Temporary for settings.
*/
@@ -5269,6 +5393,8 @@
printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME,
getModemControllerActivity(), which);
+ printCellularPerRatBreakdown(pw, sb, prefix + " ", rawRealtimeMs);
+
pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets);
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 425e797..df5b7bc 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -65,6 +65,11 @@
boolean isBatteryDischargePredictionPersonalized();
boolean isDeviceIdleMode();
boolean isLightDeviceIdleMode();
+ boolean isLowPowerStandbySupported();
+ boolean isLowPowerStandbyEnabled();
+ void setLowPowerStandbyEnabled(boolean enabled);
+ void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance);
+ void forceLowPowerStandbyActive(boolean active);
@UnsupportedAppUsage
void reboot(boolean confirm, String reason, boolean wait);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 881fced..5bd8588 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -216,6 +216,17 @@
public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;
/**
+ * Wake lock flag: This wake lock should be held by the system.
+ *
+ * <p>Meant to allow tests to keep the device awake even when power restrictions are active.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public static final int SYSTEM_WAKELOCK = 0x80000000;
+
+ /**
* Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a
* {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
* indicates that an object is not in close proximity.
@@ -2146,6 +2157,105 @@
}
/**
+ * Returns true if Low Power Standby is supported on this device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public boolean isLowPowerStandbySupported() {
+ try {
+ return mService.isLowPowerStandbySupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if Low Power Standby is enabled.
+ *
+ * <p>When Low Power Standby is enabled, apps (including apps running foreground services) are
+ * subject to additional restrictions while the device is non-interactive, outside of device
+ * idle maintenance windows: Their network access is disabled, and any wakelocks they hold are
+ * ignored.
+ *
+ * <p>When Low Power Standby is enabled or disabled, a Intent with action
+ * {@link #ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED} is broadcast to registered receivers.
+ */
+ public boolean isLowPowerStandbyEnabled() {
+ try {
+ return mService.isLowPowerStandbyEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether Low Power Standby is enabled.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @see #isLowPowerStandbyEnabled()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyEnabled(boolean enabled) {
+ try {
+ mService.setLowPowerStandbyEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether Low Power Standby should be active during doze maintenance mode.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @see #isLowPowerStandbyEnabled()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ try {
+ mService.setLowPowerStandbyActiveDuringMaintenance(activeDuringMaintenance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force Low Power Standby restrictions to be active.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void forceLowPowerStandbyActive(boolean active) {
+ try {
+ mService.forceLowPowerStandbyActive(active);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return whether the given application package name is on the device's power allowlist.
* Apps can be placed on the allowlist through the settings UI invoked by
* {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -2631,6 +2741,16 @@
= "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED";
/**
+ * Intent that is broadcast when Low Power Standby is enabled or disabled.
+ * This broadcast is only sent to registered receivers.
+ *
+ * @see #isLowPowerStandbyEnabled()
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED =
+ "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
+
+ /**
* Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
* @hide
*/
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index eb18b96..ec4d3b6 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -184,6 +184,21 @@
public abstract void setDeviceIdleTempWhitelist(int[] appids);
+ /**
+ * Updates the Low Power Standby allowlist.
+ *
+ * @param uids UIDs that are exempt from Low Power Standby restrictions
+ */
+ public abstract void setLowPowerStandbyAllowlist(int[] uids);
+
+ /**
+ * Used by LowPowerStandbyController to notify the power manager that Low Power Standby's
+ * active state has changed.
+ *
+ * @param active {@code true} to activate Low Power Standby, {@code false} to turn it off.
+ */
+ public abstract void setLowPowerStandbyActive(boolean active);
+
public abstract void startUidChanges();
public abstract void finishUidChanges();
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 8bc219b..49c0520 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -37,6 +37,7 @@
USAGE_CLASS_UNKNOWN,
USAGE_CLASS_ALARM,
USAGE_CLASS_FEEDBACK,
+ USAGE_CLASS_MEDIA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UsageClass {}
@@ -459,4 +460,3 @@
}
}
}
-
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f90679..14055ac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2386,6 +2386,15 @@
"android.settings.ENABLE_MMS_DATA_REQUEST";
/**
+ * Shows restrict settings dialog when settings is blocked.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG =
+ "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+
+ /**
* Integer value that specifies the reason triggering enable MMS data notification.
* This must be passed as an extra field to the {@link #ACTION_ENABLE_MMS_DATA_REQUEST}.
* Extra with value of EnableMmsDataReason interface.
@@ -16741,6 +16750,30 @@
public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode";
/**
+ * Setting indicating whether Low Power Standby is enabled, if supported.
+ *
+ * Values are:
+ * 0: disabled
+ * 1: enabled
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_STANDBY_ENABLED = "low_power_standby_enabled";
+
+ /**
+ * Setting indicating whether Low Power Standby is allowed to be active during doze
+ * maintenance mode.
+ *
+ * Values are:
+ * 0: Low Power Standby will be disabled during doze maintenance mode
+ * 1: Low Power Standby can be active during doze maintenance mode
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE =
+ "low_power_standby_active_during_maintenance";
+
+ /**
* Settings migrated from Wear OS settings provider.
* @hide
*/
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 86341a9..cfb6909 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -65,6 +65,16 @@
* can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain
* an {@link InlinePresentation} representing how the inline suggestion UI will be rendered.
*
+ * <a name="FillDialogUI"></a>
+ * <h3>Fill Dialog UI</h3>
+ *
+ * <p>The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. If autofill
+ * suggestions are available when the user clicks on a field that supports filling the dialog UI,
+ * Autofill will pop up a fill dialog. The dialog will take up a larger area to display the
+ * datasets, so it is easy for users to pay attention to the datasets and selecting a dataset.
+ * If the user focuses on the view before suggestions are available, will fall back to dropdown UI
+ * or inline suggestions.
+ *
* <a name="Authentication"></a>
* <h3>Dataset authentication</h3>
*
@@ -92,10 +102,9 @@
* <ol>
* <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
* {@link AutofillValue#isText() text} or is empty, all datasets are shown.
- * <li>Datasets that have a filter regex (set through
- * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
- * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
- * regex matches the view's text value converted to lower case are shown.
+ * <li>Datasets that have a filter regex (set through {@link Field.Builder#setFilter(Pattern)}
+ * and {@link Dataset.Builder#setField(AutofillId, Field)}) and whose regex matches the view's
+ * text value converted to lower case are shown.
* <li>Datasets that do not require authentication, have a field value that is
* {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
* with the lower case value of the view's text are shown.
@@ -107,11 +116,13 @@
private final ArrayList<AutofillId> mFieldIds;
private final ArrayList<AutofillValue> mFieldValues;
private final ArrayList<RemoteViews> mFieldPresentations;
+ private final ArrayList<RemoteViews> mFieldDialogPresentations;
private final ArrayList<InlinePresentation> mFieldInlinePresentations;
private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
private final ArrayList<DatasetFieldFilter> mFieldFilters;
@Nullable private final ClipData mFieldContent;
private final RemoteViews mPresentation;
+ private final RemoteViews mDialogPresentation;
@Nullable private final InlinePresentation mInlinePresentation;
@Nullable private final InlinePresentation mInlineTooltipPresentation;
private final IntentSender mAuthentication;
@@ -121,11 +132,13 @@
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
+ mFieldDialogPresentations = builder.mFieldDialogPresentations;
mFieldInlinePresentations = builder.mFieldInlinePresentations;
mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
mFieldFilters = builder.mFieldFilters;
mFieldContent = builder.mFieldContent;
mPresentation = builder.mPresentation;
+ mDialogPresentation = builder.mDialogPresentation;
mInlinePresentation = builder.mInlinePresentation;
mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mAuthentication = builder.mAuthentication;
@@ -153,6 +166,12 @@
}
/** @hide */
+ public RemoteViews getFieldDialogPresentation(int index) {
+ final RemoteViews customPresentation = mFieldDialogPresentations.get(index);
+ return customPresentation != null ? customPresentation : mDialogPresentation;
+ }
+
+ /** @hide */
public @Nullable InlinePresentation getFieldInlinePresentation(int index) {
final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
@@ -219,6 +238,9 @@
if (mFieldPresentations != null) {
builder.append(", fieldPresentations=").append(mFieldPresentations.size());
}
+ if (mFieldDialogPresentations != null) {
+ builder.append(", fieldDialogPresentations=").append(mFieldDialogPresentations.size());
+ }
if (mFieldInlinePresentations != null) {
builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
}
@@ -232,6 +254,9 @@
if (mPresentation != null) {
builder.append(", hasPresentation");
}
+ if (mDialogPresentation != null) {
+ builder.append(", hasDialogPresentation");
+ }
if (mInlinePresentation != null) {
builder.append(", hasInlinePresentation");
}
@@ -264,11 +289,13 @@
private ArrayList<AutofillId> mFieldIds;
private ArrayList<AutofillValue> mFieldValues;
private ArrayList<RemoteViews> mFieldPresentations;
+ private ArrayList<RemoteViews> mFieldDialogPresentations;
private ArrayList<InlinePresentation> mFieldInlinePresentations;
private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
private ArrayList<DatasetFieldFilter> mFieldFilters;
@Nullable private ClipData mFieldContent;
private RemoteViews mPresentation;
+ private RemoteViews mDialogPresentation;
@Nullable private InlinePresentation mInlinePresentation;
@Nullable private InlinePresentation mInlineTooltipPresentation;
private IntentSender mAuthentication;
@@ -279,7 +306,9 @@
* Creates a new builder.
*
* @param presentation The presentation used to visualize this dataset.
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
+ @Deprecated
public Builder(@NonNull RemoteViews presentation) {
Objects.requireNonNull(presentation, "presentation must be non-null");
mPresentation = presentation;
@@ -294,19 +323,34 @@
* as inline suggestions. If the dataset supports inline suggestions,
* this should not be null.
* @hide
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
@SystemApi
+ @Deprecated
public Builder(@NonNull InlinePresentation inlinePresentation) {
Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null");
mInlinePresentation = inlinePresentation;
}
/**
+ * Creates a new builder.
+ *
+ * @param presentations The presentations used to visualize this dataset.
+ */
+ public Builder(@NonNull Presentations presentations) {
+ Objects.requireNonNull(presentations, "presentations must be non-null");
+
+ mPresentation = presentations.getMenuPresentation();
+ mInlinePresentation = presentations.getInlinePresentation();
+ mInlineTooltipPresentation = presentations.getInlineTooltipPresentation();
+ mDialogPresentation = presentations.getDialogPresentation();
+ }
+
+ /**
* Creates a new builder for a dataset where each field will be visualized independently.
*
- * <p>When using this constructor, fields must be set through
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
- * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
+ * <p>When using this constructor, a presentation must be provided for each field through
+ * {@link #setField(AutofillId, Field)}.
*/
public Builder() {
}
@@ -318,7 +362,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
+ @Deprecated
public @NonNull Builder setInlinePresentation(
@NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
@@ -339,7 +385,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
+ @Deprecated
public @NonNull Builder setInlinePresentation(
@NonNull InlinePresentation inlinePresentation,
@NonNull InlinePresentation inlineTooltipPresentation) {
@@ -479,7 +527,7 @@
"Content items cannot contain an Intent: content=" + content);
}
}
- setLifeTheUniverseAndEverything(id, null, null, null, null);
+ setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
mFieldContent = content;
return this;
}
@@ -509,10 +557,12 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
- setLifeTheUniverseAndEverything(id, value, null, null, null);
+ setLifeTheUniverseAndEverything(id, value, null, null, null, null, null);
return this;
}
@@ -537,12 +587,14 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, null, null);
+ setLifeTheUniverseAndEverything(id, value, presentation, null, null, null, null);
return this;
}
@@ -572,13 +624,16 @@
* @return this builder.
* @throws IllegalStateException if the builder was constructed without a
* {@link RemoteViews presentation} or {@link #build()} was already called.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter) {
throwIfDestroyed();
Preconditions.checkState(mPresentation != null,
"Dataset presentation not set on constructor");
- setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(
+ id, value, null, null, null, new DatasetFieldFilter(filter), null);
return this;
}
@@ -610,13 +665,15 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter, @NonNull RemoteViews presentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, null,
- new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, presentation, null, null,
+ new DatasetFieldFilter(filter), null);
return this;
}
@@ -641,13 +698,16 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null);
+ setLifeTheUniverseAndEverything(
+ id, value, presentation, inlinePresentation, null, null, null);
return this;
}
@@ -672,7 +732,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation,
@NonNull InlinePresentation inlineTooltipPresentation) {
@@ -682,7 +744,7 @@
Objects.requireNonNull(inlineTooltipPresentation,
"inlineTooltipPresentation cannot be null");
setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
- inlineTooltipPresentation, null);
+ inlineTooltipPresentation, null, null);
return this;
}
@@ -718,15 +780,17 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter, @NonNull RemoteViews presentation,
@NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
- new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
+ new DatasetFieldFilter(filter), null);
return this;
}
@@ -756,7 +820,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter, @NonNull RemoteViews presentation,
@NonNull InlinePresentation inlinePresentation,
@@ -767,7 +833,91 @@
Objects.requireNonNull(inlineTooltipPresentation,
"inlineTooltipPresentation cannot be null");
setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
- inlineTooltipPresentation, new DatasetFieldFilter(filter));
+ inlineTooltipPresentation, new DatasetFieldFilter(filter), null);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field.
+ *
+ * Before Android 13, this information could be provided using several overloaded
+ * setValue(...) methods. This method replaces those with a Builder pattern.
+ * For example, in the old workflow, the app sets a field would be:
+ * <pre class="prettyprint">
+ * Dataset.Builder dataset = new Dataset.Builder();
+ * if (filter != null) {
+ * if (presentation != null) {
+ * if (inlinePresentation != null) {
+ * dataset.setValue(id, value, filter, presentation, inlinePresentation)
+ * } else {
+ * dataset.setValue(id, value, filter, presentation);
+ * }
+ * } else {
+ * dataset.setValue(id, value, filter);
+ * }
+ * } else {
+ * if (presentation != null) {
+ * if (inlinePresentation != null) {
+ * dataset.setValue(id, value, presentation, inlinePresentation)
+ * } else {
+ * dataset.setValue(id, value, presentation);
+ * }
+ * } else {
+ * dataset.setValue(id, value);
+ * }
+ * }
+ * </pre>
+ * <p>The new workflow would be:
+ * <pre class="prettyprint">
+ * Field.Builder fieldBuilder = new Field.Builder();
+ * if (value != null) {
+ * fieldBuilder.setValue(value);
+ * }
+ * if (filter != null) {
+ * fieldBuilder.setFilter(filter);
+ * }
+ * Presentations.Builder presentationsBuilder = new Presentations.Builder(id);
+ * if (presentation != null) {
+ * presentationsBuilder.setMenuPresentation(presentation);
+ * }
+ * if (inlinePresentation != null) {
+ * presentationsBuilder.setInlinePresentation(inlinePresentation);
+ * }
+ * if (dialogPresentation != null) {
+ * presentationsBuilder.setDialogPresentation(dialogPresentation);
+ * }
+ * fieldBuilder.setPresentations(presentationsBuilder.build());
+ * dataset.setField(id, fieldBuilder.build());
+ * </pre>
+ *
+ * @see Field
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param field the fill information about the field.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) {
+ throwIfDestroyed();
+ if (field == null) {
+ setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
+ } else {
+ final DatasetFieldFilter filter = field.getFilter();
+ final Presentations presentations = field.getPresentations();
+ if (presentations == null) {
+ setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
+ filter, null);
+ } else {
+ setLifeTheUniverseAndEverything(id, field.getValue(),
+ presentations.getMenuPresentation(),
+ presentations.getInlinePresentation(),
+ presentations.getInlineTooltipPresentation(), filter,
+ presentations.getDialogPresentation());
+ }
+ }
return this;
}
@@ -793,39 +943,34 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
- *
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
* @hide
*/
+ @Deprecated
@SystemApi
public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable Pattern filter,
@NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, null, inlinePresentation,
- new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, null,
+ new DatasetFieldFilter(filter), null);
return this;
}
private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
- @Nullable DatasetFieldFilter filter) {
- setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
- filter);
- }
-
- private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
- @Nullable AutofillValue value, @Nullable RemoteViews presentation,
- @Nullable InlinePresentation inlinePresentation,
@Nullable InlinePresentation tooltip,
- @Nullable DatasetFieldFilter filter) {
+ @Nullable DatasetFieldFilter filter,
+ @Nullable RemoteViews dialogPresentation) {
Objects.requireNonNull(id, "id cannot be null");
if (mFieldIds != null) {
final int existingIdx = mFieldIds.indexOf(id);
if (existingIdx >= 0) {
mFieldValues.set(existingIdx, value);
mFieldPresentations.set(existingIdx, presentation);
+ mFieldDialogPresentations.set(existingIdx, dialogPresentation);
mFieldInlinePresentations.set(existingIdx, inlinePresentation);
mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
mFieldFilters.set(existingIdx, filter);
@@ -835,6 +980,7 @@
mFieldIds = new ArrayList<>();
mFieldValues = new ArrayList<>();
mFieldPresentations = new ArrayList<>();
+ mFieldDialogPresentations = new ArrayList<>();
mFieldInlinePresentations = new ArrayList<>();
mFieldInlineTooltipPresentations = new ArrayList<>();
mFieldFilters = new ArrayList<>();
@@ -842,6 +988,7 @@
mFieldIds.add(id);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
+ mFieldDialogPresentations.add(dialogPresentation);
mFieldInlinePresentations.add(inlinePresentation);
mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
@@ -853,10 +1000,7 @@
* <p>You should not interact with this builder once this method is called.
*
* @throws IllegalStateException if no field was set (through
- * {@link #setValue(AutofillId, AutofillValue)} or
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}),
- * or if {@link #build()} was already called.
+ * {@link #setField(AutofillId, Field)}), or if {@link #build()} was already called.
*
* @return The built dataset.
*/
@@ -897,11 +1041,13 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
+ parcel.writeParcelable(mDialogPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeTypedList(mFieldIds, flags);
parcel.writeTypedList(mFieldValues, flags);
parcel.writeTypedList(mFieldPresentations, flags);
+ parcel.writeTypedList(mFieldDialogPresentations, flags);
parcel.writeTypedList(mFieldInlinePresentations, flags);
parcel.writeTypedList(mFieldInlineTooltipPresentations, flags);
parcel.writeTypedList(mFieldFilters, flags);
@@ -913,8 +1059,12 @@
public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@Override
public Dataset createFromParcel(Parcel parcel) {
- final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
- final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+ final RemoteViews presentation = parcel.readParcelable(null,
+ android.widget.RemoteViews.class);
+ final RemoteViews dialogPresentation = parcel.readParcelable(null,
+ android.widget.RemoteViews.class);
+ final InlinePresentation inlinePresentation = parcel.readParcelable(null,
+ android.service.autofill.InlinePresentation.class);
final InlinePresentation inlineTooltipPresentation =
parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
final ArrayList<AutofillId> ids =
@@ -923,27 +1073,41 @@
parcel.createTypedArrayList(AutofillValue.CREATOR);
final ArrayList<RemoteViews> presentations =
parcel.createTypedArrayList(RemoteViews.CREATOR);
+ final ArrayList<RemoteViews> dialogPresentations =
+ parcel.createTypedArrayList(RemoteViews.CREATOR);
final ArrayList<InlinePresentation> inlinePresentations =
parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<InlinePresentation> inlineTooltipPresentations =
parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<DatasetFieldFilter> filters =
parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
- final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class);
- final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
+ final ClipData fieldContent = parcel.readParcelable(null,
+ android.content.ClipData.class);
+ final IntentSender authentication = parcel.readParcelable(null,
+ android.content.IntentSender.class);
final String datasetId = parcel.readString();
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
- final Builder builder = (presentation != null) ? new Builder(presentation)
- : new Builder();
- if (inlinePresentation != null) {
- if (inlineTooltipPresentation != null) {
- builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation);
- } else {
- builder.setInlinePresentation(inlinePresentation);
+ final Builder builder;
+ if (presentation != null || inlinePresentation != null || dialogPresentation != null) {
+ final Presentations.Builder presentationsBuilder = new Presentations.Builder();
+ if (presentation != null) {
+ presentationsBuilder.setMenuPresentation(presentation);
}
+ if (inlinePresentation != null) {
+ presentationsBuilder.setInlinePresentation(inlinePresentation);
+ }
+ if (inlineTooltipPresentation != null) {
+ presentationsBuilder.setInlineTooltipPresentation(inlineTooltipPresentation);
+ }
+ if (dialogPresentation != null) {
+ presentationsBuilder.setDialogPresentation(dialogPresentation);
+ }
+ builder = new Builder(presentationsBuilder.build());
+ } else {
+ builder = new Builder();
}
if (fieldContent != null) {
@@ -954,13 +1118,15 @@
final AutofillId id = ids.get(i);
final AutofillValue value = values.get(i);
final RemoteViews fieldPresentation = presentations.get(i);
+ final RemoteViews fieldDialogPresentation = dialogPresentations.get(i);
final InlinePresentation fieldInlinePresentation =
i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
final InlinePresentation fieldInlineTooltipPresentation =
i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null;
final DatasetFieldFilter filter = filters.get(i);
builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
- fieldInlinePresentation, fieldInlineTooltipPresentation, filter);
+ fieldInlinePresentation, fieldInlineTooltipPresentation, filter,
+ fieldDialogPresentation);
}
builder.setAuthentication(authentication);
builder.setId(datasetId);
@@ -986,7 +1152,7 @@
@Nullable
public final Pattern pattern;
- private DatasetFieldFilter(@Nullable Pattern pattern) {
+ DatasetFieldFilter(@Nullable Pattern pattern) {
this.pattern = pattern;
}
diff --git a/core/java/android/service/autofill/Field.java b/core/java/android/service/autofill/Field.java
new file mode 100644
index 0000000..b7c0d82
--- /dev/null
+++ b/core/java/android/service/autofill/Field.java
@@ -0,0 +1,165 @@
+/*
+ * 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 android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.DataClass;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to set all information of a field. Such as the
+ * {@link AutofillId} of the field, the {@link AutofillValue} to be autofilled,
+ * a <a href="#Filtering">explicit filter</a>, and presentations to be visualized,
+ * etc.
+ */
+public final class Field {
+
+ /**
+ * The value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if the
+ * dataset needs authentication and you have no access to the value.
+ */
+ private @Nullable AutofillValue mValue;
+
+ /**
+ * Regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ *
+ * @see Dataset.DatasetFieldFilter
+ * @hide
+ */
+ private @Nullable Dataset.DatasetFieldFilter mFilter;
+
+ /**
+ * The presentations used to visualize this field in Autofill UI.
+ */
+ private @Nullable Presentations mPresentations;
+
+
+ /* package-private */ Field(
+ @Nullable AutofillValue value,
+ @Nullable Dataset.DatasetFieldFilter filter,
+ @Nullable Presentations presentations) {
+ this.mValue = value;
+ this.mFilter = filter;
+ this.mPresentations = presentations;
+ }
+
+ /**
+ * The value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if the
+ * dataset needs authentication and you have no access to the value.
+ */
+ @DataClass.Generated.Member
+ public @Nullable AutofillValue getValue() {
+ return mValue;
+ }
+
+ /**
+ * Regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ *
+ * @see Dataset.DatasetFieldFilter
+ * @hide
+ */
+ public @Nullable Dataset.DatasetFieldFilter getFilter() {
+ return mFilter;
+ }
+
+ /**
+ * The presentations used to visualize this field in Autofill UI.
+ */
+ public @Nullable Presentations getPresentations() {
+ return mPresentations;
+ }
+
+ /**
+ * A builder for {@link Field}
+ */
+ public static final class Builder {
+
+ private @Nullable AutofillValue mValue = null;
+ private @Nullable Dataset.DatasetFieldFilter mFilter = null;
+ private @Nullable Presentations mPresentations = null;
+ private boolean mDestroyed = false;
+
+ public Builder() {
+ }
+
+ /**
+ * The value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if the
+ * dataset needs authentication and you have no access to the value.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillValue value) {
+ checkNotUsed();
+ mValue = value;
+ return this;
+ }
+
+ /**
+ * Regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder setFilter(@Nullable Pattern value) {
+ checkNotUsed();
+ mFilter = new Dataset.DatasetFieldFilter(value);
+ return this;
+ }
+
+ /**
+ * The presentations used to visualize this field in Autofill UI.
+ */
+ public @NonNull Builder setPresentations(@NonNull Presentations value) {
+ checkNotUsed();
+ mPresentations = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Field build() {
+ checkNotUsed();
+ mDestroyed = true; // Mark builder used
+
+ Field o = new Field(
+ mValue,
+ mFilter,
+ mPresentations);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if (mDestroyed) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 43bd410..f820f03 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -98,6 +98,12 @@
// The flag value 0x20 has been defined in AutofillManager.
+ /**
+ * Indicates the request is coming from the activity just started.
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_ACTIVITY_START = 0x40;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -160,13 +166,13 @@
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
+ // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/FillRequest.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -178,7 +184,8 @@
FLAG_MANUAL_REQUEST,
FLAG_COMPATIBILITY_MODE_REQUEST,
FLAG_PASSWORD_INPUT_TYPE,
- FLAG_VIEW_NOT_FOCUSED
+ FLAG_VIEW_NOT_FOCUSED,
+ FLAG_ACTIVITY_START
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -202,6 +209,8 @@
return "FLAG_PASSWORD_INPUT_TYPE";
case FLAG_VIEW_NOT_FOCUSED:
return "FLAG_VIEW_NOT_FOCUSED";
+ case FLAG_ACTIVITY_START:
+ return "FLAG_ACTIVITY_START";
default: return Integer.toHexString(value);
}
}
@@ -264,7 +273,8 @@
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_PASSWORD_INPUT_TYPE
- | FLAG_VIEW_NOT_FOCUSED);
+ | FLAG_VIEW_NOT_FOCUSED
+ | FLAG_ACTIVITY_START);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
@@ -384,7 +394,7 @@
byte flg = in.readByte();
int id = in.readInt();
List<FillContext> fillContexts = new ArrayList<>();
- in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
+ in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
int flags = in.readInt();
InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
@@ -401,7 +411,8 @@
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_PASSWORD_INPUT_TYPE
- | FLAG_VIEW_NOT_FOCUSED);
+ | FLAG_VIEW_NOT_FOCUSED
+ | FLAG_ACTIVITY_START);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
@@ -422,10 +433,10 @@
};
@DataClass.Generated(
- time = 1589280816805L,
- codegenVersion = "1.0.15",
+ time = 1643052544776L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d94988e..903e77f 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -79,11 +79,14 @@
private final @Nullable RemoteViews mPresentation;
private final @Nullable InlinePresentation mInlinePresentation;
private final @Nullable InlinePresentation mInlineTooltipPresentation;
+ private final @Nullable RemoteViews mDialogPresentation;
+ private final @Nullable RemoteViews mDialogHeader;
private final @Nullable RemoteViews mHeader;
private final @Nullable RemoteViews mFooter;
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
+ private final @Nullable AutofillId[] mFillDialogTriggerIds;
private final long mDisableDuration;
private final @Nullable AutofillId[] mFieldClassificationIds;
private final int mFlags;
@@ -99,10 +102,13 @@
mPresentation = builder.mPresentation;
mInlinePresentation = builder.mInlinePresentation;
mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
+ mDialogPresentation = builder.mDialogPresentation;
+ mDialogHeader = builder.mDialogHeader;
mHeader = builder.mHeader;
mFooter = builder.mFooter;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
+ mFillDialogTriggerIds = builder.mFillDialogTriggerIds;
mIgnoredIds = builder.mIgnoredIds;
mDisableDuration = builder.mDisableDuration;
mFieldClassificationIds = builder.mFieldClassificationIds;
@@ -144,6 +150,16 @@
}
/** @hide */
+ public @Nullable RemoteViews getDialogPresentation() {
+ return mDialogPresentation;
+ }
+
+ /** @hide */
+ public @Nullable RemoteViews getDialogHeader() {
+ return mDialogHeader;
+ }
+
+ /** @hide */
public @Nullable RemoteViews getHeader() {
return mHeader;
}
@@ -164,6 +180,11 @@
}
/** @hide */
+ public @Nullable AutofillId[] getFillDialogTriggerIds() {
+ return mFillDialogTriggerIds;
+ }
+
+ /** @hide */
public @Nullable AutofillId[] getIgnoredIds() {
return mIgnoredIds;
}
@@ -229,6 +250,8 @@
private RemoteViews mPresentation;
private InlinePresentation mInlinePresentation;
private InlinePresentation mInlineTooltipPresentation;
+ private RemoteViews mDialogPresentation;
+ private RemoteViews mDialogHeader;
private RemoteViews mHeader;
private RemoteViews mFooter;
private IntentSender mAuthentication;
@@ -236,6 +259,7 @@
private AutofillId[] mIgnoredIds;
private long mDisableDuration;
private AutofillId[] mFieldClassificationIds;
+ private AutofillId[] mFillDialogTriggerIds;
private int mFlags;
private boolean mDestroyed;
private UserData mUserData;
@@ -243,7 +267,7 @@
private boolean mSupportsInlineSuggestions;
/**
- * Triggers a custom UI before before autofilling the screen with any data set in this
+ * Triggers a custom UI before autofilling the screen with any data set in this
* response.
*
* <p><b>Note:</b> Although the name of this method suggests that it should be used just for
@@ -277,7 +301,7 @@
* example a credit card whose CVV needs to be entered.
*
* <p>If you provide an authentication intent you must also provide a presentation
- * which is used to visualize visualize the response for triggering the authentication
+ * which is used to visualize the response for triggering the authentication
* flow.
*
* <p><b>Note:</b> Do not make the provided pending intent
@@ -306,7 +330,11 @@
* {@link #setFooter(RemoteViews) footer} are already set for this builder.
*
* @see android.app.PendingIntent#getIntentSender()
+ * @deprecated Use
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
@@ -327,7 +355,7 @@
}
/**
- * Triggers a custom UI before before autofilling the screen with any data set in this
+ * Triggers a custom UI before autofilling the screen with any data set in this
* response.
*
* <p><b>Note:</b> Although the name of this method suggests that it should be used just for
@@ -365,7 +393,11 @@
* {@link #setFooter(RemoteViews) footer} are already set for this builder.
*
* @see android.app.PendingIntent#getIntentSender()
+ * @deprecated Use
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@@ -374,13 +406,18 @@
}
/**
- * Triggers a custom UI before before autofilling the screen with any data set in this
+ * Triggers a custom UI before autofilling the screen with any data set in this
* response.
*
* <p>This method like
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)}
* but allows setting an {@link InlinePresentation} for the inline suggestion tooltip.
+ *
+ * @deprecated Use
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@@ -388,6 +425,105 @@
@Nullable InlinePresentation inlineTooltipPresentation) {
throwIfDestroyed();
throwIfDisableAutofillCalled();
+ return setAuthentication(ids, authentication, presentation,
+ inlinePresentation, inlineTooltipPresentation, null);
+ }
+
+ /**
+ * Triggers a custom UI before autofilling the screen with any data set in this
+ * response.
+ *
+ * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+ * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+ * for examples.
+ *
+ * <p>This is typically useful when a user interaction is required to unlock their
+ * data vault if you encrypt the data set labels and data set data. It is recommended
+ * to encrypt only the sensitive data and not the data set labels which would allow
+ * auth on the data set level leading to a better user experience. Note that if you
+ * use sensitive data as a label, for example an email address, then it should also
+ * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
+ * {@link Activity} which implements your authentication flow. Also if you provide an auth
+ * intent you also need to specify the presentation view to be shown in the fill UI
+ * for the user to trigger your authentication flow.
+ *
+ * <p>When a user triggers autofill, the system launches the provided intent
+ * whose extras will have the
+ * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
+ * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
+ * client state}. Once you complete your authentication flow you should set the
+ * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the
+ * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra
+ * with the fully populated {@link FillResponse response} (or {@code null} if the screen
+ * cannot be autofilled).
+ *
+ * <p>For example, if you provided an empty {@link FillResponse response} because the
+ * user's data was locked and marked that the response needs an authentication then
+ * in the response returned if authentication succeeds you need to provide all
+ * available data sets some of which may need to be further authenticated, for
+ * example a credit card whose CVV needs to be entered.
+ *
+ * <p>If you provide an authentication intent you must also provide a presentation
+ * which is used to visualize the response for triggering the authentication
+ * flow.
+ *
+ * <p><b>Note:</b> Do not make the provided pending intent
+ * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
+ * platform needs to fill in the authentication arguments.
+ *
+ * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
+ * not work with {@link InlinePresentation}.</p>
+ *
+ * @param ids id of Views that when focused will display the authentication UI.
+ * @param authentication Intent to an activity with your authentication flow.
+ * @param presentations The presentations to visualize the response.
+ *
+ * @throws IllegalArgumentException if any of the following occurs:
+ * <ul>
+ * <li>{@code ids} is {@code null}</li>
+ * <li>{@code ids} is empty</li>
+ * <li>{@code ids} contains a {@code null} element</li>
+ * <li>{@code authentication} is {@code null}, but either or both of
+ * {@code presentations.getPresentation()} and
+ * {@code presentations.getInlinePresentation()} is non-{@code null}</li>
+ * <li>{@code authentication} is non-{{@code null}, but both
+ * {@code presentations.getPresentation()} and
+ * {@code presentations.getInlinePresentation()} are {@code null}</li>
+ * </ul>
+ *
+ * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+ * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+ *
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication,
+ @Nullable Presentations presentations) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
+ if (presentations == null) {
+ return setAuthentication(ids, authentication, null, null, null, null);
+ }
+ return setAuthentication(ids, authentication,
+ presentations.getMenuPresentation(),
+ presentations.getInlinePresentation(),
+ presentations.getInlineTooltipPresentation(),
+ presentations.getDialogPresentation());
+ }
+
+ /**
+ * Triggers a custom UI before autofilling the screen with any data set in this
+ * response.
+ */
+ @NonNull
+ private Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+ @Nullable InlinePresentation inlinePresentation,
+ @Nullable InlinePresentation inlineTooltipPresentation,
+ @Nullable RemoteViews dialogPresentation) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
if (mHeader != null || mFooter != null) {
throw new IllegalStateException("Already called #setHeader() or #setFooter()");
}
@@ -400,6 +536,7 @@
mPresentation = presentation;
mInlinePresentation = inlinePresentation;
mInlineTooltipPresentation = inlineTooltipPresentation;
+ mDialogPresentation = dialogPresentation;
mAuthenticationIds = assertValid(ids);
return this;
}
@@ -552,7 +689,7 @@
*
* @throws IllegalArgumentException if {@code duration} is not a positive number.
* @throws IllegalStateException if either {@link #addDataset(Dataset)},
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)},
* {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
* {@link #setFieldClassificationIds(AutofillId...)} was already called.
*/
@@ -591,8 +728,8 @@
* @return this builder
*
* @throws IllegalStateException if an
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was
- * already set for this builder.
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
+ * authentication} was already set for this builder.
*/
// TODO(b/69796626): make it sticky / update javadoc
@NonNull
@@ -623,7 +760,7 @@
* @return this builder
*
* @throws IllegalStateException if the FillResponse
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
* requires authentication}.
*/
// TODO(b/69796626): make it sticky / update javadoc
@@ -643,7 +780,7 @@
*
* @return this builder
* @throws IllegalStateException if the FillResponse
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
* requires authentication}.
*/
@NonNull
@@ -674,13 +811,46 @@
}
/**
+ * Sets the presentation of header in fill dialog UI. The header should have
+ * a prompt for what datasets are shown in the dialog. If this is not set,
+ * the dialog only shows your application icon.
+ *
+ * More details about the fill dialog, see
+ * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+ */
+ @NonNull
+ public Builder setDialogHeader(@NonNull RemoteViews header) {
+ throwIfDestroyed();
+ Objects.requireNonNull(header);
+ mDialogHeader = header;
+ return this;
+ }
+
+ /**
+ * Sets which fields are used for the fill dialog UI.
+ *
+ * More details about the fill dialog, see
+ * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
+ */
+ @NonNull
+ public Builder setFillDialogTriggerIds(@NonNull AutofillId... ids) {
+ throwIfDestroyed();
+ Preconditions.checkArrayElementsNotNull(ids, "ids");
+ mFillDialogTriggerIds = ids;
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
* <ol>
* <li>{@link #build()} was already called.
* <li>No call was made to {@link #addDataset(Dataset)},
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)},
* {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
* {@link #setClientState(Bundle)},
* or {@link #setFieldClassificationIds(AutofillId...)}.
@@ -767,6 +937,12 @@
if (mInlineTooltipPresentation != null) {
builder.append(", hasInlineTooltipPresentation");
}
+ if (mDialogPresentation != null) {
+ builder.append(", hasDialogPresentation");
+ }
+ if (mDialogHeader != null) {
+ builder.append(", hasDialogHeader");
+ }
if (mHeader != null) {
builder.append(", hasHeader");
}
@@ -779,6 +955,10 @@
if (mAuthenticationIds != null) {
builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
}
+ if (mFillDialogTriggerIds != null) {
+ builder.append(", fillDialogTriggerIds=")
+ .append(Arrays.toString(mFillDialogTriggerIds));
+ }
builder.append(", disableDuration=").append(mDisableDuration);
if (mFlags != 0) {
builder.append(", flags=").append(mFlags);
@@ -815,6 +995,9 @@
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeParcelable(mInlineTooltipPresentation, flags);
+ parcel.writeParcelable(mDialogPresentation, flags);
+ parcel.writeParcelable(mDialogHeader, flags);
+ parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
parcel.writeParcelable(mHeader, flags);
parcel.writeParcelable(mFooter, flags);
parcel.writeParcelable(mUserData, flags);
@@ -850,9 +1033,18 @@
final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+ final RemoteViews dialogPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
if (authenticationIds != null) {
builder.setAuthentication(authenticationIds, authentication, presentation,
- inlinePresentation, inlineTooltipPresentation);
+ inlinePresentation, inlineTooltipPresentation, dialogPresentation);
+ }
+ final RemoteViews dialogHeader = parcel.readParcelable(null, android.widget.RemoteViews.class);
+ if (dialogHeader != null) {
+ builder.setDialogHeader(dialogHeader);
+ }
+ final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
+ if (triggerIds != null) {
+ builder.setFillDialogTriggerIds(triggerIds);
}
final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class);
if (header != null) {
diff --git a/core/java/android/service/autofill/Presentations.java b/core/java/android/service/autofill/Presentations.java
new file mode 100644
index 0000000..e8ac628
--- /dev/null
+++ b/core/java/android/service/autofill/Presentations.java
@@ -0,0 +1,278 @@
+/*
+ * 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 android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Holds presentations used to visualize autofill suggestions for each available UI type.
+ *
+ * @see Field
+ */
+@DataClass(genBuilder = true)
+public final class Presentations {
+
+ /**
+ * The presentation used to visualize this field in fill UI.
+ *
+ * <p>Note: Before Android 13, this was referred to simply as "presentation" in the SDK.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ private @Nullable RemoteViews mMenuPresentation;
+
+ /**
+ * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+ * If the dataset supports inline suggestions, this should not be null.
+ */
+ private @Nullable InlinePresentation mInlinePresentation;
+
+ /**
+ * The presentation used to visualize this field in the
+ * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ private @Nullable RemoteViews mDialogPresentation;
+
+ /**
+ * The {@link InlinePresentation} used to show the tooltip for the
+ * {@code mInlinePresentation}. If the set this field, the
+ * {@code mInlinePresentation} should not be null.
+ */
+ private @Nullable InlinePresentation mInlineTooltipPresentation;
+
+ private static RemoteViews defaultMenuPresentation() {
+ return null;
+ }
+
+ private static InlinePresentation defaultInlinePresentation() {
+ return null;
+ }
+
+ private static RemoteViews defaultDialogPresentation() {
+ return null;
+ }
+
+ private static InlinePresentation defaultInlineTooltipPresentation() {
+ return null;
+ }
+
+ private void onConstructed() {
+ if (mMenuPresentation == null
+ && mInlinePresentation == null
+ && mDialogPresentation == null) {
+ throw new IllegalStateException("All presentations are null.");
+ }
+ if (mInlineTooltipPresentation != null && mInlinePresentation == null) {
+ throw new IllegalStateException(
+ "The inline presentation is required for mInlineTooltipPresentation.");
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/Presentations.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ /* package-private */ Presentations(
+ @Nullable RemoteViews menuPresentation,
+ @Nullable InlinePresentation inlinePresentation,
+ @Nullable RemoteViews dialogPresentation,
+ @Nullable InlinePresentation inlineTooltipPresentation) {
+ this.mMenuPresentation = menuPresentation;
+ this.mInlinePresentation = inlinePresentation;
+ this.mDialogPresentation = dialogPresentation;
+ this.mInlineTooltipPresentation = inlineTooltipPresentation;
+
+ onConstructed();
+ }
+
+ /**
+ * The presentation used to visualize this field in fill UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RemoteViews getMenuPresentation() {
+ return mMenuPresentation;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+ * If the dataset supports inline suggestions, this should not be null.
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlinePresentation getInlinePresentation() {
+ return mInlinePresentation;
+ }
+
+ /**
+ * The presentation used to visualize this field in the fill dialog UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RemoteViews getDialogPresentation() {
+ return mDialogPresentation;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to show the tooltip for the
+ * {@code mInlinePresentation}. If the set this field, the
+ * {@code mInlinePresentation} should not be null.
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlinePresentation getInlineTooltipPresentation() {
+ return mInlineTooltipPresentation;
+ }
+
+ /**
+ * A builder for {@link Presentations}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable RemoteViews mMenuPresentation;
+ private @Nullable InlinePresentation mInlinePresentation;
+ private @Nullable RemoteViews mDialogPresentation;
+ private @Nullable InlinePresentation mInlineTooltipPresentation;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The presentation used to visualize this field in fill UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMenuPresentation(@NonNull RemoteViews value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mMenuPresentation = value;
+ return this;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+ * If the dataset supports inline suggestions, this should not be null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInlinePresentation(@NonNull InlinePresentation value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mInlinePresentation = value;
+ return this;
+ }
+
+ /**
+ * The presentation used to visualize this field in the fill dialog UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDialogPresentation(@NonNull RemoteViews value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mDialogPresentation = value;
+ return this;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to show the tooltip for the
+ * {@code mInlinePresentation}. If the set this field, the
+ * {@code mInlinePresentation} should not be null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInlineTooltipPresentation(@NonNull InlinePresentation value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mInlineTooltipPresentation = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Presentations build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mMenuPresentation = defaultMenuPresentation();
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mInlinePresentation = defaultInlinePresentation();
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mDialogPresentation = defaultDialogPresentation();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mInlineTooltipPresentation = defaultInlineTooltipPresentation();
+ }
+ Presentations o = new Presentations(
+ mMenuPresentation,
+ mInlinePresentation,
+ mDialogPresentation,
+ mInlineTooltipPresentation);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1643083242164L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/Presentations.java",
+ inputSignatures = "private @android.annotation.Nullable android.widget.RemoteViews mMenuPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlinePresentation\nprivate @android.annotation.Nullable android.widget.RemoteViews mDialogPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlineTooltipPresentation\nprivate static android.widget.RemoteViews defaultMenuPresentation()\nprivate static android.service.autofill.InlinePresentation defaultInlinePresentation()\nprivate static android.widget.RemoteViews defaultDialogPresentation()\nprivate static android.service.autofill.InlinePresentation defaultInlineTooltipPresentation()\nprivate void onConstructed()\nclass Presentations extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 31352f1..11e5ad8 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -37,5 +37,6 @@
boolean getOemUnlockEnabled();
int getFlashLockState();
boolean hasFrpCredentialHandle();
+ String getPersistentDataPackageName();
}
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 44a8862..9167153 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -26,8 +26,6 @@
import android.os.RemoteException;
import android.service.oemlock.OemLockManager;
-import com.android.internal.R;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,7 +51,6 @@
@SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE)
public class PersistentDataBlockManager {
private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
- private final Context mContext;
private IPersistentDataBlockService sService;
/**
@@ -78,10 +75,7 @@
public @interface FlashLockState {}
/** @hide */
- public PersistentDataBlockManager(
- Context context,
- IPersistentDataBlockService service) {
- mContext = context;
+ public PersistentDataBlockManager(IPersistentDataBlockService service) {
sService = service;
}
@@ -219,7 +213,12 @@
*/
@SystemApi
@NonNull
+ @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE)
public String getPersistentDataPackageName() {
- return mContext.getString(R.string.config_persistentDataPackageName);
+ try {
+ return sService.getPersistentDataPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/core/java/android/service/tracing/TraceReportService.java b/core/java/android/service/tracing/TraceReportService.java
new file mode 100644
index 0000000..3d16a3d
--- /dev/null
+++ b/core/java/android/service/tracing/TraceReportService.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.tracing;
+
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.tracing.TraceReportParams;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Service to be sub-classed and exposed by (privileged) apps which want to report
+ * system traces.
+ * <p>
+ * Subclasses should implement the onReportTrace method to handle traces reported
+ * to them.
+ * </p>
+ * <pre>
+ * public class SampleReportService extends TraceReportService {
+ * public void onReportTrace(TraceParams args) {
+ * // --- Implementation goes here ---
+ * }
+ * }
+ * </pre>
+ * <p>
+ * The service declaration in the application manifest must specify
+ * BIND_TRACE_REPORT_SERVICE in the permission attribute.
+ * </p>
+ * <pre>
+ * <application>
+ * <service android:name=".SampleReportService"
+ * android:permission="android.permission.BIND_TRACE_REPORT_SERVICE">
+ * </service>
+ * </application>
+ * </pre>
+ *
+ * Moreover, the package containing this service must hold the DUMP and PACKAGE_USAGE_STATS
+ * permissions.
+ *
+ * @hide
+ */
+@SystemApi(client = PRIVILEGED_APPS)
+public class TraceReportService extends Service {
+ private static final String TAG = "TraceReportService";
+ private Messenger mMessenger = null;
+
+ /**
+ * Public to allow this to be used by TracingServiceProxy in system_server.
+ *
+ * @hide
+ */
+ public static final int MSG_REPORT_TRACE = 1;
+
+ /**
+ * Contains information about the trace which is being reported.
+ *
+ * @hide
+ */
+ @SystemApi(client = PRIVILEGED_APPS)
+ public static final class TraceParams {
+ private final ParcelFileDescriptor mFd;
+ private final UUID mUuid;
+
+ private TraceParams(TraceReportParams params) {
+ mFd = params.fd;
+ mUuid = new UUID(params.uuidMsb, params.uuidLsb);
+ }
+
+ /**
+ * Returns the ParcelFileDescriptor for the collected trace.
+ */
+ @NonNull
+ public ParcelFileDescriptor getFd() {
+ return mFd;
+ }
+
+ /**
+ * Returns the UUID of the trace; this is exactly the UUID created by the tracing system
+ * (i.e. Perfetto) and is also present inside the trace file.
+ */
+ @NonNull
+ public UUID getUuid() {
+ return mUuid;
+ }
+ }
+
+ // Methods to override.
+ /**
+ * Called when a trace is reported and sent to this class.
+ *
+ * Note: the trace file descriptor should not be persisted beyond the lifetime of this
+ * function as it is owned by the framework and will be closed immediately after this function
+ * returns: if future use of the fd is needed, it should be duped.
+ */
+ public void onReportTrace(@NonNull TraceParams args) {
+ }
+
+ // Optional methods to override.
+ // Realistically, these methods are internal implementation details but since this class is
+ // a SystemApi, it's better to err on the side of flexibility just in-case we need to override
+ // these methods down the line.
+
+ /**
+ * Handles binder calls from system_server.
+ */
+ public boolean onMessage(@NonNull Message msg) {
+ if (msg.what == MSG_REPORT_TRACE) {
+ if (!(msg.obj instanceof TraceReportParams)) {
+ Log.e(TAG, "Received invalid type for report trace message.");
+ return false;
+ }
+ TraceParams params = new TraceParams((TraceReportParams) msg.obj);
+ try {
+ onReportTrace(params);
+ } finally {
+ try {
+ params.getFd().close();
+ } catch (IOException ignored) {
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an IBinder for handling binder calls from system_server.
+ */
+ @Nullable
+ @Override
+ public IBinder onBind(@NonNull Intent intent) {
+ if (mMessenger == null) {
+ mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage));
+ }
+ return mMessenger.getBinder();
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index fba61cf..5bd4235 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Service;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -310,9 +311,10 @@
*
* @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
*
- * TODO(b/213631672): Add CTS tests
+ * TODO(b/213631672): Remove @hide and @TestApi
* @hide
*/
+ @TestApi
public void onUserRequestedUnlock() {
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f2a0355..c91851a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -905,11 +905,12 @@
if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
return;
}
+
+ SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
if (!isPreview() && mShouldDim) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
- SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction();
// Animate dimming to gradually change the wallpaper alpha from the previous
// dim amount to the new amount only if the dim amount changed.
@@ -919,16 +920,15 @@
? 0 : DIMMING_ANIMATION_DURATION_MS);
animator.addUpdateListener((ValueAnimator va) -> {
final float dimValue = (float) va.getAnimatedValue();
- surfaceControl
- .setAlpha(mBbqSurfaceControl, 1 - dimValue)
- .apply();
+ if (mBbqSurfaceControl != null) {
+ surfaceControlTransaction
+ .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+ }
});
animator.start();
} else {
Log.v(TAG, "Setting wallpaper dimming: " + 0);
- new SurfaceControl.Transaction()
- .setAlpha(mBbqSurfaceControl, 1.0f)
- .apply();
+ surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
}
}
diff --git a/core/java/android/tracing/ITracingServiceProxy.aidl b/core/java/android/tracing/ITracingServiceProxy.aidl
index 4520db3..8029b88 100644
--- a/core/java/android/tracing/ITracingServiceProxy.aidl
+++ b/core/java/android/tracing/ITracingServiceProxy.aidl
@@ -16,17 +16,25 @@
package android.tracing;
+import android.tracing.TraceReportParams;
+
/**
* Binder interface for the TracingServiceProxy running in system_server.
*
* {@hide}
*/
-interface ITracingServiceProxy
-{
+interface ITracingServiceProxy {
/**
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
* there is no buffer available to dump.
*/
oneway void notifyTraceSessionEnded(boolean sessionStolen);
+
+ /**
+ * Notifies the specified service that a trace has been captured. The contents of |params|
+ * contains the intended recipient (package and class) of this trace as well as a file
+ * descriptor to an unlinked trace |fd| (i.e. an fd opened using O_TMPFILE).
+ */
+ oneway void reportTrace(in TraceReportParams params);
}
diff --git a/core/java/android/tracing/TraceReportParams.aidl b/core/java/android/tracing/TraceReportParams.aidl
new file mode 100644
index 0000000..f57386c
--- /dev/null
+++ b/core/java/android/tracing/TraceReportParams.aidl
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing;
+
+import android.os.ParcelFileDescriptor;
+
+/*
+ * Parameters for a trace report.
+ *
+ * See ITracingServiceProxy::reportTrace for more details.
+ *
+ * @hide
+ */
+parcelable TraceReportParams {
+ // The package name containing the reporter service (see |reporterClassName|).
+ String reporterPackageName;
+
+ // The class name of the reporter service. The framework will bind to this service and pass the
+ // trace fd and metadata to this class.
+ // This class should be "trusted" (in practice this means being a priv_app + having DUMP and
+ // USAGE_STATS permissions).
+ String reporterClassName;
+
+ // The file descriptor for the trace file. This will be an unlinked file fd (i.e. created
+ // with O_TMPFILE); the intention is that reporter classes link this fd into a app-private
+ // folder for reporting when conditions are right (e.g. charging, on unmetered networks etc).
+ ParcelFileDescriptor fd;
+
+ // The least-significant-bytes of the UUID of this trace.
+ long uuidLsb;
+
+ // The most-significant-bytes of the UUID of this trace.
+ long uuidMsb;
+
+ // Flag indicating whether, instead of passing the fd from the trace collector, to pass a
+ // pipe fd from system_server and send the file over it.
+ //
+ // This flag is necessary because there is no good way to write a CTS test where a helper
+ // priv_app (in terms of SELinux) is needed (this is because priv_apps are supposed to be
+ // preinstalled on the system partition). By creating a pipe in system_server we work around
+ // this restriction. Note that there is a maximum allowed file size if this flag is set
+ // (see TracingServiceProxy). Further note that, even though SELinux may be worked around,
+ // manifest (i.e. framework) permissions are still checked even if this flag is set.
+ boolean usePipeForTesting;
+}
\ No newline at end of file
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7559273..4ff7e229 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8178,9 +8178,13 @@
// to User. Ideally View should handle the event when isVisibleToUser()
// becomes true where it should issue notifyViewEntered().
afm.notifyViewEntered(this);
+ } else {
+ afm.enableFillRequestActivityStarted();
}
} else if (!enter && !isFocused()) {
afm.notifyViewExited(this);
+ } else if (enter) {
+ afm.enableFillRequestActivityStarted();
}
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eaa12e5..386b277 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -563,6 +563,8 @@
@Nullable
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
+ boolean mPerformAutoFill;
+
boolean mReportNextDraw;
/**
@@ -842,6 +844,7 @@
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
+ mPerformAutoFill = true;
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
@@ -4353,6 +4356,18 @@
if (mPerformContentCapture) {
performContentCaptureInitialReport();
}
+
+ if (mPerformAutoFill) {
+ notifyEnterForAutoFillIfNeeded();
+ }
+ }
+
+ private void notifyEnterForAutoFillIfNeeded() {
+ mPerformAutoFill = false;
+ final AutofillManager afm = getAutofillManager();
+ if (afm != null) {
+ afm.notifyViewEnteredForActivityStarted(mView);
+ }
}
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bb13c1e..60ccf67 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,7 @@
package android.view.autofill;
+import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
@@ -538,6 +539,8 @@
*/
public static final int NO_SESSION = Integer.MAX_VALUE;
+ private static final boolean HAS_FILL_DIALOG_UI_FEATURE = false;
+
private final IAutoFillManager mService;
private final Object mLock = new Object();
@@ -629,6 +632,29 @@
@GuardedBy("mLock")
@Nullable private Executor mRequestCallbackExecutor;
+ /**
+ * Indicates whether there are any fields that need to do a fill request
+ * after the activity starts.
+ *
+ * Note: This field will be set to true multiple times if there are many
+ * autofillable views. So needs to check mIsFillRequested at the same time to
+ * avoid re-trigger autofill.
+ */
+ private boolean mRequireAutofill;
+
+ /**
+ * Indicates whether there is already a field to do a fill request after
+ * the activity started.
+ *
+ * Autofill will automatically trigger a fill request after activity
+ * start if there is any field is autofillable. But if there is a field that
+ * triggered autofill, it is unnecessary to trigger again through
+ * AutofillManager#notifyViewEnteredForActivityStarted.
+ */
+ private boolean mIsFillRequested;
+
+ @Nullable private List<AutofillId> mFillDialogTriggerIds;
+
/** @hide */
public interface AutofillClient {
/**
@@ -766,6 +792,8 @@
mContext = Objects.requireNonNull(context, "context cannot be null");
mService = service;
mOptions = context.getAutofillOptions();
+ mIsFillRequested = false;
+ mRequireAutofill = false;
if (mOptions != null) {
sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
@@ -1042,6 +1070,39 @@
notifyViewEntered(view, 0);
}
+ /**
+ * The view is autofillable, marked to perform a fill request after layout if
+ * the field does not trigger a fill request.
+ *
+ * @hide
+ */
+ public void enableFillRequestActivityStarted() {
+ mRequireAutofill = true;
+ }
+
+ private boolean hasFillDialogUiFeature() {
+ return HAS_FILL_DIALOG_UI_FEATURE;
+ }
+
+ /**
+ * Notify autofill to do a fill request while the activity started.
+ *
+ * @hide
+ */
+ public void notifyViewEnteredForActivityStarted(@NonNull View view) {
+ if (!hasAutofillFeature() || !hasFillDialogUiFeature()) {
+ return;
+ }
+
+ if (!mRequireAutofill || mIsFillRequested) {
+ return;
+ }
+
+ int flags = FLAG_ACTIVITY_START;
+ flags |= FLAG_VIEW_NOT_FOCUSED;
+ notifyViewEntered(view, flags);
+ }
+
@GuardedBy("mLock")
private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
if (isDisabledByServiceLocked()) {
@@ -1082,6 +1143,7 @@
}
AutofillCallback callback;
synchronized (mLock) {
+ mIsFillRequested = true;
callback = notifyViewEnteredLocked(view, flags);
}
@@ -2026,6 +2088,8 @@
mFillableIds = null;
mSaveTriggerId = null;
mIdShownFillUi = null;
+ mIsFillRequested = false;
+ mRequireAutofill = false;
if (resetEnteredIds) {
mEnteredIds = null;
}
@@ -3031,6 +3095,29 @@
client.autofillClientRunOnUiThread(runnable);
}
+ private void setFillDialogTriggerIds(@Nullable List<AutofillId> ids) {
+ mFillDialogTriggerIds = ids;
+ }
+
+ /**
+ * Checks the id of autofill whether supported the fill dialog.
+ *
+ * @hide
+ */
+ public boolean isShowFillDialog(AutofillId id) {
+ if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
+ return false;
+ }
+ final int size = mFillDialogTriggerIds.size();
+ for (int i = 0; i < size; i++) {
+ AutofillId fillId = mFillDialogTriggerIds.get(i);
+ if (fillId.equalsIgnoreSession(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Implementation of the accessibility based compatibility.
*/
@@ -3736,6 +3823,13 @@
new FillCallback(callback, id));
}
}
+
+ public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.setFillDialogTriggerIds(ids));
+ }
+ }
}
private static final class AugmentedAutofillManagerClient
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 64507aa..2e5967c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -148,4 +148,9 @@
*/
void requestFillFromClient(int id, in InlineSuggestionsRequest request,
in IFillCallback callback);
+
+ /**
+ * Notifies autofill ids that require to show the fill dialog.
+ */
+ void notifyFillDialogTriggerIds(in List<AutofillId> ids);
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c6f64f4..b00a382 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -301,6 +301,13 @@
public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;
/**
+ * This mask determines which flags are propagated to nested RemoteViews (either added by
+ * addView, or set as portrait/landscape/sized RemoteViews).
+ */
+ static final int FLAG_MASK_TO_PROPAGATE =
+ FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
+
+ /**
* A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is
* intentionally a different instance in order to trick Bundle reader so that it doesn't allow
* lazy initialization.
@@ -467,6 +474,18 @@
*/
public void addFlags(@ApplyFlags int flags) {
mApplyFlags = mApplyFlags | flags;
+
+ int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE;
+ if (flagsToPropagate != 0) {
+ if (hasSizedRemoteViews()) {
+ for (RemoteViews remoteView : mSizedRemoteViews) {
+ remoteView.addFlags(flagsToPropagate);
+ }
+ } else if (hasLandscapeAndPortraitLayouts()) {
+ mLandscape.addFlags(flagsToPropagate);
+ mPortrait.addFlags(flagsToPropagate);
+ }
+ }
}
/**
@@ -2407,6 +2426,10 @@
// will return -1.
final int nextChild = getNextRecyclableChild(target);
RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
+
+ int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE;
+ if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate);
+
if (nextChild >= 0 && mStableId != NO_ID) {
// At that point, the views starting at index nextChild are the ones recyclable but
// not yet recycled. All views added on that round of application are placed before.
@@ -2419,8 +2442,8 @@
target.removeViews(nextChild, recycledViewIndex - nextChild);
}
setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
- rvToApply.reapply(context, child, handler, null /* size */, colorResources,
- false /* topLevel */);
+ rvToApply.reapplyNestedViews(context, child, rootParent, handler,
+ null /* size */, colorResources);
return;
}
// If we cannot recycle the views, we still remove all views in between to
@@ -2431,8 +2454,8 @@
// If we cannot recycle, insert the new view before the next recyclable child.
// Inflate nested views and add as children
- View nestedView = rvToApply.apply(context, target, handler, null /* size */,
- colorResources);
+ View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler,
+ null /* size */, colorResources);
if (mStableId != NO_ID) {
setStableId(nestedView, mStableId);
}
@@ -3780,7 +3803,7 @@
* @param parcel
*/
public RemoteViews(Parcel parcel) {
- this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0);
+ this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
}
private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@@ -5580,6 +5603,16 @@
return result;
}
+ private View applyNestedViews(Context context, ViewGroup directParent,
+ ViewGroup rootParent, InteractionHandler handler, SizeF size,
+ ColorResources colorResources) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+ View result = inflateView(context, rvToApply, directParent, 0, colorResources);
+ rvToApply.performApply(result, rootParent, handler, colorResources);
+ return result;
+ }
+
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
return inflateView(context, rv, parent, 0, null);
}
@@ -5895,6 +5928,12 @@
}
}
+ private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
+ InteractionHandler handler, SizeF size, ColorResources colorResources) {
+ RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+ rvToApply.performApply(v, rootParent, handler, colorResources);
+ }
+
/**
* Applies all the actions to the provided view, moving as much of the task on the background
* thread as possible.
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index fd1e848..3fa62e0 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -599,6 +599,7 @@
private final Rect mTransitionBounds = new Rect();
private HardwareBuffer mThumbnail;
private int mAnimations;
+ private @ColorInt int mBackgroundColor;
private AnimationOptions(int type) {
mType = type;
@@ -608,6 +609,7 @@
mType = in.readInt();
mEnterResId = in.readInt();
mExitResId = in.readInt();
+ mBackgroundColor = in.readInt();
mOverrideTaskTransition = in.readBoolean();
mPackageName = in.readString();
mTransitionBounds.readFromParcel(in);
@@ -624,11 +626,12 @@
}
public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
- int exitResId, boolean overrideTaskTransition) {
+ int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
options.mPackageName = packageName;
options.mEnterResId = enterResId;
options.mExitResId = exitResId;
+ options.mBackgroundColor = backgroundColor;
options.mOverrideTaskTransition = overrideTaskTransition;
return options;
}
@@ -673,6 +676,10 @@
return mExitResId;
}
+ public @ColorInt int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
public boolean getOverrideTaskTransition() {
return mOverrideTaskTransition;
}
@@ -698,6 +705,7 @@
dest.writeInt(mType);
dest.writeInt(mEnterResId);
dest.writeInt(mExitResId);
+ dest.writeInt(mBackgroundColor);
dest.writeBoolean(mOverrideTaskTransition);
dest.writeString(mPackageName);
mTransitionBounds.writeToParcel(dest, flags);
@@ -740,7 +748,7 @@
@Override
public String toString() {
- return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+ return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
+ " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
}
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 9648008..ee20847 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -113,7 +113,7 @@
void notePhoneOn();
void notePhoneOff();
void notePhoneSignalStrength(in SignalStrength signalStrength);
- void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType);
+ void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency);
void notePhoneState(int phoneState);
void noteWifiOn();
void noteWifiOff();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 25ee2d0..4fc977f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -69,10 +69,14 @@
import android.os.connectivity.WifiActivityEnergyInfo;
import android.os.connectivity.WifiBatteryStats;
import android.provider.Settings;
+import android.telephony.Annotation.NetworkType;
import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
+import android.telephony.ServiceState.RegState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -935,6 +939,119 @@
final StopwatchTimer[] mPhoneDataConnectionsTimer =
new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
+ @RadioAccessTechnology
+ int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER;
+
+ private static class RadioAccessTechnologyBatteryStats {
+ /**
+ * This RAT is currently being used.
+ */
+ private boolean mActive = false;
+ /**
+ * Current active frequency range for this RAT.
+ */
+ @ServiceState.FrequencyRange
+ private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+ /**
+ * Current signal strength for this RAT.
+ */
+ private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ /**
+ * Timers for each combination of frequency range and signal strength.
+ */
+ public final StopwatchTimer[][] perStateTimers;
+
+ RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) {
+ perStateTimers =
+ new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+ for (int i = 0; i < freqCount; i++) {
+ for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+ perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase);
+ }
+ }
+ }
+
+ /**
+ * Note this RAT is currently being used.
+ */
+ public void noteActive(boolean active, long elapsedRealtimeMs) {
+ if (mActive == active) return;
+ mActive = active;
+ if (mActive) {
+ perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked(
+ elapsedRealtimeMs);
+ } else {
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ }
+
+ /**
+ * Note current frequency range has changed.
+ */
+ public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange,
+ long elapsedRealtimeMs) {
+ if (mFrequencyRange == frequencyRange) return;
+
+ if (!mActive) {
+ // RAT not in use, note the frequency change and move on.
+ mFrequencyRange = frequencyRange;
+ return;
+ }
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+ perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs);
+ mFrequencyRange = frequencyRange;
+ }
+
+ /**
+ * Note current signal strength has changed.
+ */
+ public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) {
+ if (mSignalStrength == signalStrength) return;
+
+ if (!mActive) {
+ // RAT not in use, note the signal strength change and move on.
+ mSignalStrength = signalStrength;
+ return;
+ }
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+ perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs);
+ mSignalStrength = signalStrength;
+ }
+
+ /**
+ * Reset display timers.
+ */
+ public void reset(long elapsedRealtimeUs) {
+ final int size = perStateTimers.length;
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+ perStateTimers[i][j].reset(false, elapsedRealtimeUs);
+ }
+ }
+ }
+ }
+
+ /**
+ * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange}
+ */
+ private static final int NR_FREQUENCY_COUNT = 5;
+
+ RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats =
+ new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT];
+
+ @GuardedBy("this")
+ private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked(
+ @RadioAccessTechnology int rat) {
+ RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+ if (stats == null) {
+ final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1;
+ stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase);
+ mPerRatBatteryStats[rat] = stats;
+ }
+ return stats;
+ }
+
final LongSamplingCounter[] mNetworkByteActivityCounters =
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
@@ -5886,6 +6003,10 @@
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mMobileRadioPowerState = powerState;
+
+ // Inform current RatBatteryStats that the modem active state might have changed.
+ getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs);
+
if (active) {
mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs);
mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs);
@@ -6307,21 +6428,86 @@
@GuardedBy("this")
public void notePhoneSignalStrengthLocked(SignalStrength signalStrength,
long elapsedRealtimeMs, long uptimeMs) {
- // Bin the strength.
- int bin = signalStrength.getLevel();
- updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin,
+ final int overallSignalStrength = signalStrength.getLevel();
+ final SparseIntArray perRatSignalStrength = new SparseIntArray(
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT);
+
+ // Extract signal strength level for each RAT.
+ final List<CellSignalStrength> cellSignalStrengths =
+ signalStrength.getCellSignalStrengths();
+ final int size = cellSignalStrengths.size();
+ for (int i = 0; i < size; i++) {
+ CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i);
+ // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology
+ final int ratType;
+ final int level;
+ if (cellSignalStrength instanceof CellSignalStrengthNr) {
+ ratType = RADIO_ACCESS_TECHNOLOGY_NR;
+ level = cellSignalStrength.getLevel();
+ } else if (cellSignalStrength instanceof CellSignalStrengthLte) {
+ ratType = RADIO_ACCESS_TECHNOLOGY_LTE;
+ level = cellSignalStrength.getLevel();
+ } else {
+ ratType = RADIO_ACCESS_TECHNOLOGY_OTHER;
+ level = cellSignalStrength.getLevel();
+ }
+
+ // According to SignalStrength#getCellSignalStrengths(), multiple of the same
+ // cellSignalStrength can be present. Just take the highest level one for each RAT.
+ if (perRatSignalStrength.get(ratType, -1) < level) {
+ perRatSignalStrength.put(ratType, level);
+ }
+ }
+
+ notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength,
+ elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Note phone signal strength change, including per RAT signal strength.
+ *
+ * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+ * @param perRatSignalStrength signal strength of available RATs
+ */
+ @GuardedBy("this")
+ public void notePhoneSignalStrengthLocked(int signalStrength,
+ SparseIntArray perRatSignalStrength) {
+ notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength,
+ mClock.elapsedRealtime(), mClock.uptimeMillis());
+ }
+
+ /**
+ * Note phone signal strength change, including per RAT signal strength.
+ *
+ * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+ * @param perRatSignalStrength signal strength of available RATs
+ */
+ @GuardedBy("this")
+ public void notePhoneSignalStrengthLocked(int signalStrength,
+ SparseIntArray perRatSignalStrength,
+ long elapsedRealtimeMs, long uptimeMs) {
+ // Note each RAT's signal strength.
+ final int size = perRatSignalStrength.size();
+ for (int i = 0; i < size; i++) {
+ final int rat = perRatSignalStrength.keyAt(i);
+ final int ratSignalStrength = perRatSignalStrength.valueAt(i);
+ getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs);
+ }
+ updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength,
elapsedRealtimeMs, uptimeMs);
}
@UnsupportedAppUsage
@GuardedBy("this")
- public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) {
- notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
+ public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+ @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) {
+ notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency,
mClock.elapsedRealtime(), mClock.uptimeMillis());
}
@GuardedBy("this")
- public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType,
+ public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+ @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency,
long elapsedRealtimeMs, long uptimeMs) {
// BatteryStats uses 0 to represent no network type.
// Telephony does not have a concept of no network type, and uses 0 to represent unknown.
@@ -6344,6 +6530,13 @@
}
}
}
+
+ final int newRat = mapNetworkTypeToRadioAccessTechnology(bin);
+ if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ // Note possible frequency change for the NR RAT.
+ getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs);
+ }
+
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
@@ -6357,6 +6550,45 @@
}
mPhoneDataConnectionType = bin;
mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs);
+
+ if (mActiveRat != newRat) {
+ getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs);
+ mActiveRat = newRat;
+ }
+ final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked();
+ getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs);
+ }
+ }
+
+ @RadioAccessTechnology
+ private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) {
+ switch (dataType) {
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return RADIO_ACCESS_TECHNOLOGY_NR;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return RADIO_ACCESS_TECHNOLOGY_LTE;
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough
+ return RADIO_ACCESS_TECHNOLOGY_OTHER;
+ default:
+ Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER");
+ return RADIO_ACCESS_TECHNOLOGY_OTHER;
}
}
@@ -7731,6 +7963,23 @@
return mPhoneDataConnectionsTimer[dataType];
}
+ @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs) {
+ final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+ if (stats == null) return 0L;
+
+ final int freqCount = stats.perStateTimers.length;
+ if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L;
+
+ final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange];
+ final int strengthCount = strengthTimers.length;
+ if (signalStrength < 0 || signalStrength >= strengthCount) return 0L;
+
+ return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked(
+ elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000;
+ }
+
@UnsupportedAppUsage
@Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
@@ -12553,6 +12802,11 @@
mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs);
mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs);
}
+ for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) {
+ final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i];
+ if (stats == null) continue;
+ stats.reset(elapsedRealtimeUs);
+ }
mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs);
mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs);
mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs);
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index be91aac..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1159,6 +1159,17 @@
: Integer.compare(a.transactionCode, b.transactionCode);
}
+ /** @hide */
+ public static void startForBluetooth(Context context) {
+ new BinderCallsStats.SettingsObserver(
+ context,
+ new BinderCallsStats(
+ new BinderCallsStats.Injector(),
+ com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
+ }
+
+
/**
* Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index bfe4323..17b84ff 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.ColorInt;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -72,9 +73,30 @@
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
+ return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null));
+ }
+
+ /**
+ * Returns a default user icon in a particular color.
+ *
+ * @param resources resources object to fetch the user icon
+ * @param color the color used for the icon
+ */
+ public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) {
Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
+ icon.setColorFilter(color, Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
+
+ /**
+ * Returns an array containing colors to be used for default user icons.
+ */
+ public static int[] getUserIconColors(Resources resources) {
+ int[] result = new int[USER_ICON_COLORS.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = resources.getColor(USER_ICON_COLORS[i], null);
+ }
+ return result;
+ }
}
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index b25b4f7..f462523 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -85,7 +85,7 @@
sp<GraphicBuffer> buffer = new GraphicBuffer(width, height, pixelFormat, layers,
grallocUsage, std::string("HardwareBuffer pid [") + std::to_string(getpid()) +"]");
status_t error = buffer->initCheck();
- if (error < 0) {
+ if (error != OK) {
if (kDebugGraphicBuffer) {
ALOGW("createGraphicBuffer() failed in HardwareBuffer.create()");
}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d039bcf..78b403c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -133,6 +133,18 @@
MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern));
}
+uint64_t htonll(uint64_t ll) {
+ constexpr uint32_t kBytesToTest = 0x12345678;
+ constexpr uint8_t kFirstByte = (const uint8_t &)kBytesToTest;
+ constexpr bool kIsLittleEndian = kFirstByte == 0x78;
+
+ if constexpr (kIsLittleEndian) {
+ return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 | htonl(ll >> 32);
+ } else {
+ return ll;
+ }
+}
+
static jstring getJavaInternedString(JNIEnv *env, const String8 &string) {
if (string == "") {
return gStringOffsets.emptyString;
@@ -193,7 +205,8 @@
int32_t id = nativeSensor.getId();
env->CallVoidMethod(sensor, sensorOffsets.setId, id);
Sensor::uuid_t uuid = nativeSensor.getUuid();
- env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
+ env->CallVoidMethod(sensor, sensorOffsets.setUuid, htonll(uuid.i64[0]),
+ htonll(uuid.i64[1]));
}
return sensor;
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index e13b788..6b82ba8 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1288,7 +1288,7 @@
audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
- if (jAudioProfile == nullptr) {
+ if (*jAudioProfile == nullptr) {
return AUDIO_JAVA_ERROR;
}
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
index 1be1873..9734ab9 100644
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ b/core/jni/android_server_NetworkManagementSocketTagger.cpp
@@ -61,27 +61,9 @@
return (jint)res;
}
-static jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) {
- int res = qtaguid_setCounterSet(setNum, uid);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) {
- int res = qtaguid_deleteTagData(tagNum, uid);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
static const JNINativeMethod gQTagUidMethods[] = {
{ "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
{ "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
- { "native_setCounterSet", "(II)I", (void*)setCounterSet},
- { "native_deleteTagData", "(II)I", (void*)deleteTagData},
};
int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index acb7429..d48ea3b 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -127,7 +127,7 @@
optional bool is_light_device_idle_mode = 25;
// True if we are currently in device idle mode.
optional bool is_device_idle_mode = 26;
- // Set of app ids that we will always respect the wake locks for.
+ // Set of app ids that we will respect the wake locks for while in device idle mode.
repeated int32 device_idle_whitelist = 27;
// Set of app ids that are temporarily allowed to acquire wakelocks due to
// high-pri message
@@ -187,6 +187,8 @@
// Whether or not the current enhanced discharge prediction is personalized based on device
// usage or not.
optional bool is_enhanced_discharge_prediction_personalized = 54;
+ optional bool is_low_power_standby_active = 55;
+ optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
}
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
@@ -209,6 +211,8 @@
// When this wake lock is released, poke the user activity timer
// so the screen stays on for a little longer.
optional bool is_on_after_release = 2;
+ // The wakelock is held by the system server on request by another app.
+ optional bool system_wakelock = 3;
}
optional .android.os.WakeLockLevelEnum lock_level = 1;
@@ -428,3 +432,39 @@
// Next tag: 23
}
+
+message LowPowerStandbyControllerDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // True if Low Power Standby is active
+ optional bool is_active = 1;
+
+ // True if Low Power Standby is enabled
+ optional bool is_enabled = 2;
+
+ // True if Low Power Standby is supported
+ optional bool is_supported_config = 3;
+
+ // True if Low Power Standby is enabled by default
+ optional bool is_enabled_by_default_config = 4;
+
+ // True if the device is currently interactive
+ optional bool is_interactive = 5;
+
+ // Time (in elapsedRealtime) when the device was last interactive
+ optional bool last_interactive_time = 6;
+
+ // Time (in milliseconds) after becoming non-interactive that Low Power Standby can activate
+ optional int32 standby_timeout_config = 7;
+
+ // True if the device has entered idle mode since becoming non-interactive
+ optional int32 idle_since_non_interactive = 8;
+
+ // True if the device is currently in idle mode
+ optional int32 is_device_idle = 9;
+
+ // Set of app ids that are exempt form low power standby
+ repeated int32 allowlist = 10;
+
+ // Next tag: 11
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a0e426d..d21852d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -107,6 +107,7 @@
<protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+ <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED" />
<protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
<!-- @deprecated This is rarely used and will be phased out soon. -->
@@ -3662,6 +3663,13 @@
<permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
android:protectionLevel="signature|privileged|development" />
+ <!-- @hide @SystemApi Must be required by a
+ {@link com.android.service.tracing.TraceReportService}, to ensure that only the system
+ can bind to it.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @hide @SystemApi @TestApi
Allow an application to approve incident and bug reports to be
shared off-device. There can be only one application installed on the
@@ -4956,6 +4964,11 @@
<permission android:name="android.permission.USER_ACTIVITY"
android:protectionLevel="signature|privileged" />
+ <!-- @hide @SystemApi Allows an application to manage Low Power Standby settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY"
+ android:protectionLevel="signature|privileged" />
+
<!-- @hide Allows low-level access to tun tap driver -->
<permission android:name="android.permission.NET_TUNNELING"
android:protectionLevel="signature" />
@@ -5403,7 +5416,7 @@
android:protectionLevel="signature|setup|role" />
<!-- Allows access to keyguard secure storage. Only allowed for system processes.
- @hide -->
+ @hide @TestApi -->
<permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
android:protectionLevel="signature|setup" />
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
new file mode 100644
index 0000000..cce9593
--- /dev/null
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -0,0 +1,115 @@
+<?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.
+-->
+
+<!-- NOTE: outer layout is required to provide proper shadow. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/autofill_dialog_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/autofill_save_outer_top_margin"
+ android:padding="@dimen/autofill_save_outer_top_padding"
+ android:elevation="@dimen/autofill_elevation"
+ android:background="?android:attr/colorBackground"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/autofill_service_icon"
+ android:scaleType="fitStart"
+ android:visibility="gone"
+ android:layout_width="@dimen/autofill_dialog_icon_size"
+ android:layout_height="@dimen/autofill_dialog_icon_size"/>
+
+ <LinearLayout
+ android:id="@+id/autofill_dialog_header"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:visibility="gone"
+ android:foreground="?attr/listChoiceBackgroundIndicator"
+ style="@style/AutofillDatasetPicker" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/autofill_dialog_container"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:visibility="gone"
+ android:foreground="?attr/listChoiceBackgroundIndicator"
+ style="@style/AutofillDatasetPicker" />
+
+ <ListView
+ android:id="@+id/autofill_dialog_list"
+ android:layout_weight="1"
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:drawSelectorOnTop="true"
+ android:clickable="true"
+ android:divider="@null"
+ android:visibility="gone"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:foreground="?attr/listChoiceBackgroundIndicator"
+ style="@style/AutofillDatasetPicker" />
+
+ <com.android.internal.widget.ButtonBarLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:padding="@dimen/autofill_save_button_bar_padding"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/autofill_dialog_no"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/dismiss_action">
+ </Button>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible">
+ </Space>
+
+ <Button
+ android:id="@+id/autofill_dialog_yes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Widget.DeviceDefault.Button.Colored"
+ android:text="@string/autofill_save_yes"
+ android:visibility="gone" >
+ </Button>
+
+ </com.android.internal.widget.ButtonBarLayout>
+
+</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 74cd519..0edc0d6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2707,6 +2707,9 @@
Values are bandwidth_estimator, carrier_config and modem. -->
<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">false</bool>
+
<!-- Whether WiFi display is supported by this device.
There are many prerequisites for this feature to work correctly.
Here are a few of them:
@@ -5624,4 +5627,14 @@
<!-- Whether or not to enable the lock screen entry point for the QR code scanner. -->
<bool name="config_enableQrCodeScannerOnLockScreen">false</bool>
+
+ <!-- Whether Low Power Standby is supported and can be enabled. -->
+ <bool name="config_lowPowerStandbySupported">false</bool>
+
+ <!-- If supported, whether Low Power Standby is enabled by default. -->
+ <bool name="config_lowPowerStandbyEnabledByDefault">false</bool>
+
+ <!-- The amount of time after becoming non-interactive (in ms) after which
+ Low Power Standby can activate. -->
+ <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 771c072..4874e65 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -877,6 +877,9 @@
<!-- Maximum number of datasets that are visible in the UX picker without scrolling -->
<integer name="autofill_max_visible_datasets">3</integer>
+ <!-- Size of an icon in the Autolfill fill dialog -->
+ <dimen name="autofill_dialog_icon_size">56dp</dimen>
+
<!-- Size of a slice shortcut view -->
<dimen name="slice_shortcut_size">56dp</dimen>
<!-- Size of action icons in a slice -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8d51dbe..602d5f9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -472,6 +472,7 @@
<java-symbol type="integer" name="config_mobile_mtu" />
<java-symbol type="array" name="config_mobile_tcp_buffers" />
<java-symbol type="string" name="config_tcp_buffers" />
+ <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
<java-symbol type="integer" name="config_volte_replacement_rat"/>
<java-symbol type="integer" name="config_valid_wappush_index" />
<java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -3510,6 +3511,7 @@
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
+ <java-symbol type="layout" name="autofill_fill_dialog"/>
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_dataset_footer"/>
<java-symbol type="id" name="autofill_dataset_header"/>
@@ -3522,6 +3524,13 @@
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_yes" />
+ <java-symbol type="id" name="autofill_service_icon" />
+ <java-symbol type="id" name="autofill_dialog_picker"/>
+ <java-symbol type="id" name="autofill_dialog_header"/>
+ <java-symbol type="id" name="autofill_dialog_container"/>
+ <java-symbol type="id" name="autofill_dialog_list"/>
+ <java-symbol type="id" name="autofill_dialog_no" />
+ <java-symbol type="id" name="autofill_dialog_yes" />
<java-symbol type="string" name="autofill_error_cannot_autofill" />
<java-symbol type="string" name="autofill_picker_no_suggestions" />
<java-symbol type="string" name="autofill_picker_some_suggestions" />
@@ -4672,4 +4681,8 @@
<java-symbol type="string" name="notification_content_abusive_bg_apps"/>
<java-symbol type="string" name="notification_content_long_running_fgs"/>
<java-symbol type="string" name="notification_action_check_bg_apps"/>
+
+ <java-symbol type="bool" name="config_lowPowerStandbySupported" />
+ <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
+ <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />
</resources>
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
new file mode 100644
index 0000000..713a4c8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
new file mode 100644
index 0000000..74c939b
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/themed_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/RelativeLayoutAlignTop25Alpha"/>
diff --git a/core/tests/coretests/res/layout/remote_views_light_background_text.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
new file mode 100644
index 0000000..f300f09
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/light_background_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_views_list.xml b/core/tests/coretests/res/layout/remote_views_list.xml
new file mode 100644
index 0000000..ca43bc8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_views_list.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 352b4dc..32eebb35 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -34,6 +34,16 @@
<style name="LayoutInDisplayCutoutModeAlways">
<item name="android:windowLayoutInDisplayCutoutMode">always</item>
</style>
+ <style name="RelativeLayoutAlignBottom50Alpha">
+ <item name="android:layout_alignParentTop">false</item>
+ <item name="android:layout_alignParentBottom">true</item>
+ <item name="android:alpha">0.5</item>
+ </style>
+ <style name="RelativeLayoutAlignTop25Alpha">
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:layout_alignParentBottom">false</item>
+ <item name="android:alpha">0.25</item>
+ </style>
<style name="WindowBackgroundColorLiteral">
<item name="android:windowBackground">#00FF00</item>
</style>
diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
similarity index 96%
rename from core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
rename to core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 632a1a9..e11fe17 100644
--- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view;
+package android.view.stylus;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -33,6 +33,12 @@
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,7 +53,7 @@
* Tests for {@link HandwritingInitiator}
*
* Build/Install/Run:
- * atest FrameworksCoreTests:HandwritingInitiatorTest
+ * atest FrameworksCoreTests:android.view.stylus.HandwritingInitiatorTest
*/
@Presubmit
@SmallTest
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 059c764..00b3693 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,8 +16,12 @@
package android.widget;
+import static com.android.internal.R.id.pending_intent_tag;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -31,7 +35,10 @@
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Looper;
import android.os.Parcel;
+import android.util.SizeF;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
@@ -49,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
/**
@@ -261,6 +269,148 @@
verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
}
+ @Test
+ public void nestedViews_setRemoteAdapter_intent() {
+ Looper.prepare();
+
+ AppWidgetHostView widget = new AppWidgetHostView(mContext);
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+ inner2.setRemoteAdapter(R.id.list, new Intent());
+ inner1.addView(R.id.container, inner2);
+ top.addView(R.id.container, inner1);
+
+ View view = top.apply(mContext, widget);
+ widget.addView(view);
+
+ ListView listView = (ListView) view.findViewById(R.id.list);
+ listView.onRemoteAdapterConnected();
+ assertNotNull(listView.getAdapter());
+
+ top.reapply(mContext, view);
+ listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+ }
+
+ @Test
+ public void nestedViews_setRemoteAdapter_remoteCollectionItems() {
+ AppWidgetHostView widget = new AppWidgetHostView(mContext);
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+ inner2.setRemoteAdapter(
+ R.id.list,
+ new RemoteViews.RemoteCollectionItems.Builder()
+ .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host))
+ .build());
+ inner1.addView(R.id.container, inner2);
+ top.addView(R.id.container, inner1);
+
+ View view = top.apply(mContext, widget);
+ widget.addView(view);
+
+ ListView listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+
+ top.reapply(mContext, view);
+ listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+ }
+
+ @Test
+ public void nestedViews_collectionChildFlag() throws Exception {
+ RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+ nested.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
+ listItem.addView(R.id.container, nested);
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void landscapePortraitViews_collectionChildFlag() throws Exception {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(inner, inner);
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void sizedViews_collectionChildFlag() throws Exception {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(
+ Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void nestedViews_lightBackgroundLayoutFlag() {
+ RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+ nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host);
+ parent.addView(R.id.container, nested);
+ parent.setLightBackgroundLayoutId(R.layout.remote_view_host);
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
+
+ @Test
+ public void landscapePortraitViews_lightBackgroundLayoutFlag() {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(inner, inner);
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
+ @Test
+ public void sizedViews_lightBackgroundLayoutFlag() {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(
+ Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
private RemoteViews createViewChained(int depth, String... texts) {
RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
@@ -483,6 +633,47 @@
index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag));
}
+ @Test
+ public void nestedViews_themesPropagateCorrectly() {
+ Context themedContext =
+ new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha);
+ RelativeLayout rootParent = new RelativeLayout(themedContext);
+
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+ RemoteViews inner1 =
+ new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme);
+ RemoteViews inner2 =
+ new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+
+ inner1.addView(R.id.themed_layout, inner2);
+ top.addView(R.id.container, inner1);
+
+ RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent);
+ assertEquals(0.5, root.getAlpha(), 0.);
+ RelativeLayout.LayoutParams rootParams =
+ (RelativeLayout.LayoutParams) root.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+ // The theme is set on inner1View and its descendants. However, inner1View does
+ // not get its layout params from its theme (though its descendants do), but other
+ // attributes such as alpha are set.
+ RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0);
+ assertEquals(R.id.themed_layout, inner1View.getId());
+ assertEquals(0.25, inner1View.getAlpha(), 0.);
+ RelativeLayout.LayoutParams inner1Params =
+ (RelativeLayout.LayoutParams) inner1View.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+ RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0);
+ assertEquals(0.25, inner2View.getAlpha(), 0.);
+ RelativeLayout.LayoutParams inner2Params =
+ (RelativeLayout.LayoutParams) inner2View.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP));
+ }
+
private class WidgetContainer extends AppWidgetHostView {
int[] mSharedViewIds;
String[] mSharedViewNames;
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index b655369..f5cbffb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -17,6 +17,7 @@
package com.android.internal.os;
import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
@@ -30,6 +31,12 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.telephony.Annotation;
+import android.telephony.CellSignalStrength;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.view.Display;
@@ -1165,6 +1172,185 @@
"D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
}
+ @SmallTest
+ public void testGetPerStateActiveRadioDurationMs() {
+ final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+ final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
+ final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+ final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+ final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+ for (int rat = 0; rat < ratCount; rat++) {
+ for (int freq = 0; freq < frequencyCount; freq++) {
+ for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+ expectedDurationsMs[rat][freq][txLvl] = 0;
+ }
+ }
+ }
+
+ class ModemAndBatteryState {
+ public long currentTimeMs = 100;
+ public boolean onBattery = false;
+ public boolean modemActive = false;
+ @Annotation.NetworkType
+ public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ @BatteryStats.RadioAccessTechnology
+ public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+ @ServiceState.FrequencyRange
+ public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+ public SparseIntArray currentSignalStrengths = new SparseIntArray();
+
+ void setOnBattery(boolean onBattery) {
+ this.onBattery = onBattery;
+ bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
+ currentTimeMs * 1000);
+ }
+
+ void setModemActive(boolean active) {
+ modemActive = active;
+ final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
+ }
+
+ void setRatType(@Annotation.NetworkType int dataType,
+ @BatteryStats.RadioAccessTechnology int rat) {
+ currentNetworkDataType = dataType;
+ currentRat = rat;
+ bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
+ currentFrequencyRange);
+ }
+
+ void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
+ currentFrequencyRange = frequency;
+ bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
+ ServiceState.STATE_IN_SERVICE, frequency);
+ }
+
+ void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
+ currentSignalStrengths.put(rat, strength);
+ final int size = currentSignalStrengths.size();
+ final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
+ bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
+ }
+ }
+ final ModemAndBatteryState state = new ModemAndBatteryState();
+
+ IntConsumer incrementTime = inc -> {
+ state.currentTimeMs += inc;
+ clock.realtime = clock.uptime = state.currentTimeMs;
+
+ // If the device is not on battery, no timers should increment.
+ if (!state.onBattery) return;
+ // If the modem is not active, no timers should increment.
+ if (!state.modemActive) return;
+
+ final int currentRat = state.currentRat;
+ final int currentFrequencyRange =
+ currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+ int currentSignalStrength = state.currentSignalStrengths.get(currentRat);
+ expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc;
+ };
+
+ state.setOnBattery(false);
+ state.setModemActive(false);
+ state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // While not on battery, the timers should not increase.
+ state.setModemActive(true);
+ incrementTime.accept(100);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+ incrementTime.accept(200);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ incrementTime.accept(500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+ incrementTime.accept(300);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
+ incrementTime.accept(400);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+ incrementTime.accept(500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+ // start counting up.
+ state.setOnBattery(true);
+ incrementTime.accept(600);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Changing LTE signal strength should be tracked.
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ incrementTime.accept(700);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ incrementTime.accept(800);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ incrementTime.accept(900);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+ incrementTime.accept(1000);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Change in the signal strength of nonactive RAT should not affect anything.
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ incrementTime.accept(1100);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Changing to OTHER Rat should start tracking the poor signal strength.
+ state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+ incrementTime.accept(1200);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Noting frequency change should not affect non NR Rat.
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+ incrementTime.accept(1300);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+ state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+ incrementTime.accept(1400);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Noting frequency change should not affect non NR Rat.
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+ incrementTime.accept(1500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Modem no longer active, should not be tracking any more.
+ state.setModemActive(false);
+ incrementTime.accept(1500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
@@ -1238,4 +1424,30 @@
bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
}
}
+
+ private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs,
+ BatteryStatsImpl bi, long currentTimeMs) {
+ for (int rat = 0; rat < expectedDurationsMs.length; rat++) {
+ final long[][] expectedRatDurationsMs = expectedDurationsMs[rat];
+ for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) {
+ final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq];
+ for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) {
+ final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength];
+ final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq,
+ strength, currentTimeMs);
+
+ // Build a verbose fail message, just in case.
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Wrong time in state for RAT:");
+ sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(", frequency:");
+ sb.append(ServiceState.frequencyRangeToString(freq));
+ sb.append(", strength:");
+ sb.append(strength);
+
+ assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs);
+ }
+ }
+ }
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e68b1ac..b3dcc34 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -334,6 +334,7 @@
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_GAME_MODE"/>
+ <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
<permission name="android.permission.MANAGE_ROLLBACKS"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 84cf285a..76b4036 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -36,16 +36,16 @@
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
- <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string>
- <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు పూర్తి స్క్రీన్"</string>
- <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్"</string>
+ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 13e81bd..79c8a87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -371,10 +371,14 @@
}
if (a.getShowBackground()) {
- // use the window's background color if provided as the background color for the
- // animation - the top most window with a valid background color and
- // showBackground set takes precedence.
- if (change.getBackgroundColor() != 0) {
+ if (info.getAnimationOptions().getBackgroundColor() != 0) {
+ // If available use the background color provided through AnimationOptions
+ backgroundColorForTransition =
+ info.getAnimationOptions().getBackgroundColor();
+ } else if (change.getBackgroundColor() != 0) {
+ // Otherwise default to the window's background color if provided through
+ // the theme as the background color for the animation - the top most window
+ // with a valid background color and showBackground set takes precedence.
backgroundColorForTransition = change.getBackgroundColor();
}
}
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 8ad8abc..25ed935 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,19 +16,18 @@
#ifndef DRAWFRAMETASK_H
#define DRAWFRAMETASK_H
-#include <optional>
-#include <vector>
-
-#include <performance_hint_private.h>
+#include <android/performance_hint.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <utils/StrongPointer.h>
-#include "RenderTask.h"
+#include <optional>
+#include <vector>
#include "../FrameInfo.h"
#include "../Rect.h"
#include "../TreeInfo.h"
+#include "RenderTask.h"
namespace android {
namespace uirenderer {
diff --git a/libs/tracingproxy/Android.bp b/libs/tracingproxy/Android.bp
index 7126bfa..23d107b 100644
--- a/libs/tracingproxy/Android.bp
+++ b/libs/tracingproxy/Android.bp
@@ -37,6 +37,7 @@
srcs: [
":ITracingServiceProxy.aidl",
+ ":TraceReportParams.aidl",
],
shared_libs: [
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index aa076e8..fd8a06d 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -401,10 +401,14 @@
ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
const uint8_t *data, size_t numBytes, int64_t timestamp) {
- if (inputPort == nullptr || data == nullptr) {
+ if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) {
return AMEDIA_ERROR_INVALID_PARAMETER;
}
+ if (numBytes == 0) {
+ return 0;
+ }
+
// AMIDI_logBuffer(data, numBytes);
uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35c794e..f9a1774 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -315,18 +315,18 @@
AThermal_registerThermalStatusListener; # introduced=30
AThermal_unregisterThermalStatusListener; # introduced=30
AThermal_getThermalHeadroom; # introduced=31
+ APerformanceHint_getManager; # introduced=Tiramisu
+ APerformanceHint_createSession; # introduced=Tiramisu
+ APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
+ APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
+ APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
+ APerformanceHint_closeSession; # introduced=Tiramisu
local:
*;
};
LIBANDROID_PLATFORM {
global:
- APerformanceHint_getManager;
- APerformanceHint_createSession;
- APerformanceHint_getPreferredUpdateRateNanos;
- APerformanceHint_updateTargetWorkDuration;
- APerformanceHint_reportActualWorkDuration;
- APerformanceHint_closeSession;
APerformanceHint_setIHintManagerForTesting;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 51a0c99..0c36051 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,17 +16,18 @@
#define LOG_TAG "perf_hint"
-#include <utility>
-#include <vector>
-
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
+#include <utility>
+#include <vector>
+
using namespace android;
using namespace android::os;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 284e9ee..b17850e 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -18,10 +18,12 @@
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
#include <binder/IBinder.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <performance_hint_private.h>
+
#include <memory>
#include <vector>
diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
index 1eb52fb..8bb12a6d 100644
--- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
+++ b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
@@ -111,21 +111,6 @@
public int statsUid = -1;
}
- public static void setKernelCounterSet(int uid, int counterSet) {
- final int errno = native_setCounterSet(counterSet, uid);
- if (errno < 0) {
- Log.w(TAG, "setKernelCountSet(" + uid + ", " + counterSet + ") failed with errno "
- + errno);
- }
- }
-
- public static void resetKernelUidStats(int uid) {
- int errno = native_deleteTagData(0, uid);
- if (errno < 0) {
- Log.w(TAG, "problem clearing counters for uid " + uid + " : errno " + errno);
- }
- }
-
/**
* Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
* format like {@code 0x7fffffff00000000}.
@@ -141,6 +126,4 @@
private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
private static native int native_untagSocketFd(FileDescriptor fd);
- private static native int native_setCounterSet(int uid, int counterSetNum);
- private static native int native_deleteTagData(int tag, int uid);
}
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 8e584d0..748b0ae 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -59,7 +59,6 @@
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -121,6 +120,7 @@
import android.service.NetworkInterfaceProto;
import android.service.NetworkStatsServiceDumpProto;
import android.system.ErrnoException;
+import android.system.Os;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionPlan;
import android.text.TextUtils;
@@ -546,6 +546,10 @@
return null;
}
}
+
+ public TagStatsDeleter getTagStatsDeleter() {
+ return NetworkStatsService::nativeDeleteTagData;
+ }
}
/**
@@ -1801,7 +1805,10 @@
// Clear kernel stats associated with UID
for (int uid : uids) {
- resetKernelUidStats(uid);
+ final int ret = mDeps.getTagStatsDeleter().deleteTagData(uid);
+ if (ret < 0) {
+ Log.w(TAG, "problem clearing counters for uid " + uid + ": " + Os.strerror(-ret));
+ }
}
}
@@ -2380,4 +2387,12 @@
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
private static native long nativeGetUidStat(int uid, int type);
+
+ // TODO: use BpfNetMaps to delete tag data and remove this.
+ @VisibleForTesting
+ interface TagStatsDeleter {
+ int deleteTagData(int uid);
+ }
+
+ private static native int nativeDeleteTagData(int uid);
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index fcf2282..684f4de 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -50,6 +50,7 @@
"SettingsLibSettingsTransition",
"SettingsLibActivityEmbedding",
"SettingsLibButtonPreference",
+ "setupdesign",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index a347345..13f8a37 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,4 +18,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib">
+ <application>
+ <activity
+ android:name="com.android.settingslib.users.AvatarPickerActivity"
+ android:theme="@style/SudThemeGlifV2.DayNight"/>
+ </application>
+
</manifest>
diff --git a/packages/SettingsLib/res/drawable/add_a_photo_circled.xml b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml
new file mode 100644
index 0000000..bcfd221
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="?android:attr/colorAccent"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/add_a_photo_circled_padding"
+ android:right="@dimen/add_a_photo_circled_padding"
+ android:top="@dimen/add_a_photo_circled_padding"
+ android:bottom="@dimen/add_a_photo_circled_padding"
+ android:drawable="@drawable/ic_add_a_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
new file mode 100644
index 0000000..97aec74
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="2dp"
+ android:color="?android:attr/colorPrimary"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/avatar_picker_icon_inset"
+ android:right="@dimen/avatar_picker_icon_inset"
+ android:top="@dimen/avatar_picker_icon_inset"
+ android:bottom="@dimen/avatar_picker_icon_inset"
+ android:drawable="@drawable/ic_avatar_choose_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml
new file mode 100644
index 0000000..ccde597
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_selector.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true">
+ <shape android:shape="oval">
+ <stroke
+ android:color="?android:attr/colorPrimary"
+ android:width="@dimen/avatar_picker_padding"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
new file mode 100644
index 0000000..7033aae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="2dp"
+ android:color="?android:attr/colorPrimary"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/avatar_picker_icon_inset"
+ android:right="@dimen/avatar_picker_icon_inset"
+ android:top="@dimen/avatar_picker_icon_inset"
+ android:bottom="@dimen/avatar_picker_icon_inset"
+ android:drawable="@drawable/ic_avatar_take_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
new file mode 100644
index 0000000..0cc54b6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M5.85,17.1q1.275,-0.975 2.85,-1.538Q10.275,15 12,15q1.725,0 3.3,0.563 1.575,0.562 2.85,1.537 0.875,-1.025 1.363,-2.325Q20,13.475 20,12q0,-3.325 -2.337,-5.662Q15.325,4 12,4T6.338,6.338Q4,8.675 4,12q0,1.475 0.487,2.775 0.488,1.3 1.363,2.325zM12,13q-1.475,0 -2.488,-1.012Q8.5,10.975 8.5,9.5t1.012,-2.487Q10.525,6 12,6t2.488,1.013Q15.5,8.024 15.5,9.5t-1.012,2.488Q13.475,13 12,13zM12,22q-2.075,0 -3.9,-0.788 -1.825,-0.787 -3.175,-2.137 -1.35,-1.35 -2.137,-3.175Q2,14.075 2,12t0.788,-3.9q0.787,-1.825 2.137,-3.175 1.35,-1.35 3.175,-2.137Q9.925,2 12,2t3.9,0.788q1.825,0.787 3.175,2.137 1.35,1.35 2.137,3.175Q22,9.925 22,12t-0.788,3.9q-0.787,1.825 -2.137,3.175 -1.35,1.35 -3.175,2.137Q14.075,22 12,22zM12,20q1.325,0 2.5,-0.387 1.175,-0.388 2.15,-1.113 -0.975,-0.725 -2.15,-1.113Q13.325,17 12,17t-2.5,0.387q-1.175,0.388 -2.15,1.113 0.975,0.725 2.15,1.113Q10.675,20 12,20zM12,11q0.65,0 1.075,-0.425 0.425,-0.425 0.425,-1.075 0,-0.65 -0.425,-1.075Q12.65,8 12,8q-0.65,0 -1.075,0.425Q10.5,8.85 10.5,9.5q0,0.65 0.425,1.075Q11.35,11 12,11zM12,9.5zM12,18.5z"/>
+</vector>
+
diff --git a/packages/SettingsLib/res/drawable/ic_add_a_photo.xml b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml
new file mode 100644
index 0000000..4e35503
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M11.5,17.5q1.875,0 3.188,-1.313Q16,14.876 16,13q0,-1.875 -1.313,-3.188Q13.376,8.5 11.5,8.5q-1.875,0 -3.188,1.313Q7,11.124 7,13q0,1.875 1.313,3.188Q9.624,17.5 11.5,17.5zM3.5,21q-0.825,0 -1.413,-0.587Q1.5,19.825 1.5,19L1.5,7q0,-0.825 0.587,-1.412Q2.675,5 3.5,5h3.15L8.5,3h6v4h-11v12h16v-9h2v9q0,0.825 -0.587,1.413Q20.325,21 19.5,21zM18.5,8L18.5,6h-2L16.5,4h2L18.5,2h2v2h2v2h-2v2zM11.5,13z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
new file mode 100644
index 0000000..b85fdc2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
new file mode 100644
index 0000000..5c56276
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M12,17.5q1.875,0 3.188,-1.313Q16.5,14.876 16.5,13q0,-1.875 -1.313,-3.188Q13.876,8.5 12,8.5q-1.875,0 -3.188,1.313Q7.5,11.124 7.5,13q0,1.875 1.313,3.188Q10.124,17.5 12,17.5zM4,21q-0.825,0 -1.413,-0.587Q2,19.825 2,19L2,7q0,-0.825 0.587,-1.412Q3.175,5 4,5h3.15L9,3h6l1.85,2L20,5q0.825,0 1.413,0.588Q22,6.175 22,7v12q0,0.825 -0.587,1.413Q20.825,21 20,21zM20,19L20,7L4,7v12zM4,19L4,7v12z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/avatar_item.xml b/packages/SettingsLib/res/layout/avatar_item.xml
new file mode 100644
index 0000000..c52f664
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_item.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.
+ -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/avatar_image"
+ android:layout_height="@dimen/avatar_size_in_picker"
+ android:layout_width="@dimen/avatar_size_in_picker"
+ android:layout_margin="@dimen/avatar_picker_margin"
+ android:layout_gravity="center"
+ android:padding="@dimen/avatar_picker_padding"
+ android:background="@drawable/avatar_selector"/>
diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml
new file mode 100644
index 0000000..2d40bd0
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_picker.xml
@@ -0,0 +1,38 @@
+<?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.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/glif_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:icon="@drawable/ic_account_circle_outline"
+ app:sucUsePartnerResource="true"
+ app:sucHeaderText="@string/avatar_picker_title">
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:gravity="center_horizontal"
+ style="@style/SudContentFrame">
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/avatar_grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index f66ff00..c8ddcc8 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -18,24 +18,32 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="false"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:padding="16dp">
- <ImageView
- android:id="@+id/user_photo"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:layout_gravity="bottom"
- android:contentDescription="@string/user_image_photo_selector"
- android:background="@*android:drawable/spinner_background_holo_dark"
- android:scaleType="fitCenter"/>
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+ <ImageView
+ android:id="@+id/user_photo"
+ android:layout_width="@dimen/user_photo_size_in_profile_info_dialog"
+ android:layout_height="@dimen/user_photo_size_in_profile_info_dialog"
+ android:contentDescription="@string/user_image_photo_selector"
+ android:scaleType="fitCenter"/>
+ <ImageView
+ android:id="@+id/add_a_photo_icon"
+ android:layout_width="@dimen/add_a_photo_icon_size_in_profile_info_dialog"
+ android:layout_height="@dimen/add_a_photo_icon_size_in_profile_info_dialog"
+ android:src="@drawable/add_a_photo_circled"
+ android:layout_gravity="bottom|right" />
+ </FrameLayout>
<EditText
android:id="@+id/user_name"
- android:layout_width="0dp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_weight="1"
+ android:layout_gravity="center"
android:minWidth="200dp"
android:layout_marginStart="6dp"
android:minHeight="@dimen/min_tap_target_size"
diff --git a/packages/SettingsLib/res/values-w1280dp-land/dimens.xml b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">104dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1440dp-land/dimens.xml b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">128dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1600dp-land/dimens.xml b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">148dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w480dp-port/dimens.xml b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
new file mode 100644
index 0000000..cab78d6
--- /dev/null
+++ b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">3</integer>
+ <dimen name="avatar_size_in_picker">112dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values-w600dp-port/dimens.xml b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">104dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w720dp-port/dimens.xml b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">128dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w840dp-port/dimens.xml b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">148dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w960dp-land/dimens.xml b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
new file mode 100644
index 0000000..8403dba
--- /dev/null
+++ b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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.
+ -->
+<resources>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">96dp</dimen>
+ <dimen name="avatar_picker_padding">6dp</dimen>
+ <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 2b5e9cd..93e3dee 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -647,4 +647,6 @@
<item>disabled</item>
</array>
+ <array name="avatar_images"/>
+
</resources>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 9bccc3f..120df76 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -98,4 +98,15 @@
<!-- Minimum width for the popup for updating a user's photo. -->
<dimen name="update_user_photo_popup_min_width">300dp</dimen>
+ <dimen name="add_a_photo_circled_padding">6dp</dimen>
+ <dimen name="user_photo_size_in_profile_info_dialog">112dp</dimen>
+ <dimen name="add_a_photo_icon_size_in_profile_info_dialog">32dp</dimen>
+
+ <integer name="avatar_picker_columns">3</integer>
+ <dimen name="avatar_size_in_picker">96dp</dimen>
+ <dimen name="avatar_picker_padding">6dp</dimen>
+ <dimen name="avatar_picker_margin">2dp</dimen>
+
+ <dimen name="avatar_picker_icon_inset">25dp</dimen>
+
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e4eab4b..45f8f1d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1164,6 +1164,9 @@
<!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
<string name="disabled_by_admin_summary_text">Controlled by admin</string>
+ <!-- Summary for settings preference disabled by app ops [CHAR LIMIT=50] -->
+ <string name="disabled_by_app_ops_text">Controlled by Restricted Setting</string>
+
<!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. -->
<string name="disabled">Disabled</string>
<!-- Summary of app trusted to install apps [CHAR LIMIT=45] -->
@@ -1545,4 +1548,7 @@
<!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_no_calling">No calling.</string>
+
+ <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
+ <string name="avatar_picker_title">Choose a profile picture</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 1e8cb9f..1573edb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -26,14 +26,17 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
@@ -42,6 +45,7 @@
import android.view.MenuItem;
import android.widget.TextView;
+import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
@@ -729,6 +733,26 @@
}
/**
+ * Show restricted setting dialog.
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public static void sendShowRestrictedSettingDialogIntent(Context context,
+ String packageName, int uid) {
+ final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Get restricted settings dialog intent.
+ */
+ private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
+ final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ return intent;
+ }
+
+ /**
* Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes.
* {@link LockPatternUtils} is an internal API not supported by robolectric.
* {@link DevicePolicyManager} has a {@code getProfileParent} not yet suppored by robolectric.
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index fc8b587..81146fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -19,6 +19,7 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.content.Context;
+import android.os.Process;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.View;
@@ -37,9 +38,14 @@
RestrictedPreferenceHelper mHelper;
public RestrictedPreference(Context context, AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
+ int defStyleAttr, int defStyleRes, String packageName, int uid) {
super(context, attrs, defStyleAttr, defStyleRes);
- mHelper = new RestrictedPreferenceHelper(context, this, attrs);
+ mHelper = new RestrictedPreferenceHelper(context, this, attrs, packageName, uid);
+ }
+
+ public RestrictedPreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ this(context, attrs, defStyleAttr, defStyleRes, null, Process.INVALID_UID);
}
public RestrictedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -55,6 +61,11 @@
this(context, null);
}
+ public RestrictedPreference(Context context, String packageName, int uid) {
+ this(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle), 0, packageName, uid);
+ }
+
@Override
protected int getSecondTargetResId() {
return R.layout.restricted_icon;
@@ -115,7 +126,21 @@
}
}
+ public void setDisabledByAppOps(boolean disabled) {
+ if (mHelper.setDisabledByAppOps(disabled)) {
+ notifyChanged();
+ }
+ }
+
public boolean isDisabledByAdmin() {
return mHelper.isDisabledByAdmin();
}
+
+ public int getUid() {
+ return mHelper != null ? mHelper.uid : Process.INVALID_UID;
+ }
+
+ public String getPackageName() {
+ return mHelper != null ? mHelper.packageName : null;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 83a6973..3f322d6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -20,6 +20,7 @@
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;
@@ -29,6 +30,8 @@
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.
@@ -36,16 +39,22 @@
public class RestrictedPreferenceHelper {
private final Context mContext;
private final Preference mPreference;
+ final String packageName;
+ final int uid;
private boolean mDisabledByAdmin;
private EnforcedAdmin mEnforcedAdmin;
private String mAttrUserRestriction = null;
- private boolean mUseAdminDisabledSummary = false;
+ private boolean mDisabledSummary = false;
+
+ private boolean mDisabledByAppOps;
public RestrictedPreferenceHelper(Context context, Preference preference,
- AttributeSet attrs) {
+ AttributeSet attrs, String packageName, int uid) {
mContext = context;
mPreference = preference;
+ this.packageName = packageName;
+ this.uid = uid;
if (attrs != null) {
final TypedArray attributes = context.obtainStyledAttributes(attrs,
@@ -71,27 +80,34 @@
final TypedValue useAdminDisabledSummary =
attributes.peekValue(R.styleable.RestrictedPreference_useAdminDisabledSummary);
if (useAdminDisabledSummary != null) {
- mUseAdminDisabledSummary =
+ mDisabledSummary =
(useAdminDisabledSummary.type == TypedValue.TYPE_INT_BOOLEAN
&& useAdminDisabledSummary.data != 0);
}
}
}
+ public RestrictedPreferenceHelper(Context context, Preference preference,
+ AttributeSet attrs) {
+ this(context, preference, attrs, null, android.os.Process.INVALID_UID);
+ }
+
/**
* Modify PreferenceViewHolder to add padlock if restriction is disabled.
*/
public void onBindViewHolder(PreferenceViewHolder holder) {
- if (mDisabledByAdmin) {
+ if (mDisabledByAdmin || mDisabledByAppOps) {
holder.itemView.setEnabled(true);
}
- if (mUseAdminDisabledSummary) {
+ if (mDisabledSummary) {
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView != null) {
final CharSequence disabledText = summaryView.getContext().getText(
R.string.disabled_by_admin_summary_text);
if (mDisabledByAdmin) {
summaryView.setText(disabledText);
+ } else if (mDisabledByAppOps) {
+ summaryView.setText(R.string.disabled_by_app_ops_text);
} else if (TextUtils.equals(disabledText, summaryView.getText())) {
// It's previously set to disabled text, clear it.
summaryView.setText(null);
@@ -101,7 +117,7 @@
}
public void useAdminDisabledSummary(boolean useSummary) {
- mUseAdminDisabledSummary = useSummary;
+ mDisabledSummary = useSummary;
}
/**
@@ -109,11 +125,19 @@
*
* @return true if the method handled the click.
*/
+ @SuppressWarnings("NewApi")
public boolean performClick() {
if (mDisabledByAdmin) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
return true;
}
+ if (mDisabledByAppOps) {
+ Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU,
+ "Build SDK version needs >= T");
+ RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
+ uid);
+ return true;
+ }
return false;
}
@@ -166,14 +190,34 @@
changed = true;
}
- if (!(mPreference instanceof RestrictedTopLevelPreference)) {
- mPreference.setEnabled(!disabled);
+ updateDisabledState();
+
+ return changed;
+ }
+
+ public boolean setDisabledByAppOps(boolean disabled) {
+ boolean changed = false;
+ if (mDisabledByAppOps != disabled) {
+ mDisabledByAppOps = disabled;
+ changed = true;
}
+ updateDisabledState();
+
return changed;
}
public boolean isDisabledByAdmin() {
return mDisabledByAdmin;
}
+
+ public boolean isDisabledByAppOps() {
+ return mDisabledByAppOps;
+ }
+
+ private void updateDisabledState() {
+ if (!(mPreference instanceof RestrictedTopLevelPreference)) {
+ mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
new file mode 100644
index 0000000..9dfc8ea
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
@@ -0,0 +1,112 @@
+/*
+ * 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.settingslib.applications;
+
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.LruCache;
+
+/**
+ * Cache app icon for management.
+ */
+public class AppIconCacheManager {
+ private static final String TAG = "AppIconCacheManager";
+ private static final float CACHE_RATIO = 0.1f;
+ private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
+ private static final String DELIMITER = ":";
+ private static AppIconCacheManager sAppIconCacheManager;
+ private final LruCache<String, Drawable> mDrawableCache;
+
+ private AppIconCacheManager() {
+ mDrawableCache = new LruCache<String, Drawable>(MAX_CACHE_SIZE_IN_KB) {
+ @Override
+ protected int sizeOf(String key, Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024;
+ }
+ // Rough estimate each pixel will use 4 bytes by default.
+ return drawable.getIntrinsicHeight() * drawable.getIntrinsicWidth() * 4 / 1024;
+ }
+ };
+ }
+
+ /**
+ * Get an {@link AppIconCacheManager} instance.
+ */
+ public static synchronized AppIconCacheManager getInstance() {
+ if (sAppIconCacheManager == null) {
+ sAppIconCacheManager = new AppIconCacheManager();
+ }
+ return sAppIconCacheManager;
+ }
+
+ /**
+ * Put app icon to cache
+ *
+ * @param packageName of icon
+ * @param uid of packageName
+ * @param drawable app icon
+ */
+ public void put(String packageName, int uid, Drawable drawable) {
+ final String key = getKey(packageName, uid);
+ if (key == null || drawable == null || drawable.getIntrinsicHeight() < 0
+ || drawable.getIntrinsicWidth() < 0) {
+ Log.w(TAG, "Invalid key or drawable.");
+ return;
+ }
+ mDrawableCache.put(key, drawable);
+ }
+
+ /**
+ * Get app icon from cache.
+ *
+ * @param packageName of icon
+ * @param uid of packageName
+ * @return app icon
+ */
+ public Drawable get(String packageName, int uid) {
+ final String key = getKey(packageName, uid);
+ if (key == null) {
+ Log.w(TAG, "Invalid key with package or uid.");
+ return null;
+ }
+ final Drawable cachedDrawable = mDrawableCache.get(key);
+ return cachedDrawable != null ? cachedDrawable.mutate() : null;
+ }
+
+ /**
+ * Release cache.
+ */
+ public static void release() {
+ if (sAppIconCacheManager != null) {
+ sAppIconCacheManager.mDrawableCache.evictAll();
+ }
+ }
+
+ private static String getKey(String packageName, int uid) {
+ if (packageName == null || uid < 0) {
+ return null;
+ }
+ return packageName + DELIMITER + UserHandle.getUserId(uid);
+ }
+
+ private static int getMaxCacheInKb() {
+ return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index a5da8b6..cc4fef8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
import android.hardware.usb.IUsbManager;
import android.net.Uri;
import android.os.Environment;
@@ -35,7 +36,9 @@
import android.util.Log;
import com.android.settingslib.R;
+import com.android.settingslib.Utils;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
@@ -212,4 +215,82 @@
UserHandle.myUserId());
return TextUtils.equals(packageName, defaultBrowserPackage);
}
+
+ /**
+ * Get the app icon by app entry.
+ *
+ * @param context caller's context
+ * @param appEntry AppEntry of ApplicationsState
+ * @return app icon of the app entry
+ */
+ public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) {
+ if (appEntry == null || appEntry.info == null) {
+ return null;
+ }
+
+ final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance();
+ final String packageName = appEntry.info.packageName;
+ final int uid = appEntry.info.uid;
+
+ Drawable icon = appIconCacheManager.get(packageName, uid);
+ if (icon == null) {
+ if (appEntry.apkFile != null && appEntry.apkFile.exists()) {
+ icon = Utils.getBadgedIcon(context, appEntry.info);
+ appIconCacheManager.put(packageName, uid, icon);
+ } else {
+ setAppEntryMounted(appEntry, /* mounted= */ false);
+ icon = context.getDrawable(
+ com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
+ }
+ } else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) {
+ // If the app wasn't mounted but is now mounted, reload its icon.
+ setAppEntryMounted(appEntry, /* mounted= */ true);
+ icon = Utils.getBadgedIcon(context, appEntry.info);
+ appIconCacheManager.put(packageName, uid, icon);
+ }
+
+ return icon;
+ }
+
+ /**
+ * Get the app icon from cache by app entry.
+ *
+ * @param appEntry AppEntry of ApplicationsState
+ * @return app icon of the app entry
+ */
+ public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) {
+ return appEntry == null || appEntry.info == null ? null
+ : AppIconCacheManager.getInstance().get(
+ appEntry.info.packageName,
+ appEntry.info.uid);
+ }
+
+ /**
+ * Preload the top N icons of app entry list.
+ *
+ * @param context caller's context
+ * @param appEntries AppEntry list of ApplicationsState
+ * @param number the number of Top N icons of the appEntries
+ */
+ public static void preloadTopIcons(Context context,
+ ArrayList<ApplicationsState.AppEntry> appEntries, int number) {
+ if (appEntries == null || appEntries.isEmpty() || number <= 0) {
+ return;
+ }
+
+ for (int i = 0; i < Math.min(appEntries.size(), number); i++) {
+ final ApplicationsState.AppEntry entry = appEntries.get(i);
+ ThreadUtils.postOnBackgroundThread(() -> {
+ getIcon(context, entry);
+ });
+ }
+ }
+
+ private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) {
+ if (appEntry.mounted != mounted) {
+ synchronized (appEntry) {
+ appEntry.mounted = mounted;
+ }
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f046f06..fdb0607 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -95,6 +95,7 @@
private static final Object sLock = new Object();
private static final Pattern REMOVE_DIACRITICALS_PATTERN
= Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+ private static final String SETTING_PKG = "com.android.settings";
@VisibleForTesting
static ApplicationsState sInstance;
@@ -492,6 +493,9 @@
return null;
}
+ /**
+ * Starting Android T, this method will not be used if {@link AppIconCacheManager} is applied.
+ */
public void ensureIcon(AppEntry entry) {
if (entry.icon != null) {
return;
@@ -758,6 +762,10 @@
return null;
}
+ private static boolean isAppIconCacheEnabled(Context context) {
+ return SETTING_PKG.equals(context.getPackageName());
+ }
+
void rebuildActiveSessions() {
synchronized (mEntriesMap) {
if (!mSessionsChanged) {
@@ -806,6 +814,11 @@
} else {
mHasLifecycle = false;
}
+
+ if (isAppIconCacheEnabled(mContext)) {
+ // Skip the preloading all icons step to save memory usage.
+ mFlags = mFlags & ~FLAG_SESSION_REQUEST_ICONS;
+ }
}
@SessionFlags
@@ -814,7 +827,12 @@
}
public void setSessionFlags(@SessionFlags int flags) {
- mFlags = flags;
+ if (isAppIconCacheEnabled(mContext)) {
+ // Skip the preloading all icons step to save memory usage.
+ mFlags = flags & ~FLAG_SESSION_REQUEST_ICONS;
+ } else {
+ mFlags = flags;
+ }
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@@ -1576,6 +1594,10 @@
// Need to synchronize on 'this' for the following.
public ApplicationInfo info;
+ /**
+ * Starting Android T, this field will not be used if {@link AppIconCacheManager} is
+ * applied.
+ */
public Drawable icon;
public String sizeStr;
public String internalSizeStr;
@@ -1596,15 +1618,11 @@
this.size = SIZE_UNKNOWN;
this.sizeStale = true;
ensureLabel(context);
- // Speed up the cache of the icon and label description if they haven't been created.
- ThreadUtils.postOnBackgroundThread(() -> {
- if (this.icon == null) {
- this.ensureIconLocked(context);
- }
- if (this.labelDescription == null) {
- this.ensureLabelDescriptionLocked(context);
- }
- });
+ // Speed up the cache of the label description if they haven't been created.
+ if (this.labelDescription == null) {
+ ThreadUtils.postOnBackgroundThread(
+ () -> this.ensureLabelDescriptionLocked(context));
+ }
}
public void ensureLabel(Context context) {
@@ -1620,7 +1638,15 @@
}
}
+ /**
+ * Starting Android T, this method will not be used if {@link AppIconCacheManager} is
+ * applied.
+ */
boolean ensureIconLocked(Context context) {
+ if (isAppIconCacheEnabled(context)) {
+ return false;
+ }
+
if (this.icon == null) {
if (this.apkFile.exists()) {
this.icon = Utils.getBadgedIcon(context, info);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
new file mode 100644
index 0000000..61b8911
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -0,0 +1,297 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.StrictMode;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.util.EventLog;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class AvatarPhotoController {
+ private static final String TAG = "AvatarPhotoController";
+
+ private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+ // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
+ // so we need a default photo size
+ private static final int DEFAULT_PHOTO_SIZE = 500;
+
+ private static final String IMAGES_DIR = "multi_user";
+ private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+ private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
+
+ private final int mPhotoSize;
+
+ private final AvatarPickerActivity mActivity;
+ private final String mFileAuthority;
+
+ private final File mImagesDir;
+ private final Uri mCropPictureUri;
+ private final Uri mTakePictureUri;
+
+ AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
+ mActivity = activity;
+ mFileAuthority = fileAuthority;
+
+ mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+ mImagesDir.mkdir();
+ mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = getPhotoSize(activity);
+ }
+
+ /**
+ * Handles activity result from containing activity/fragment after a take/choose/crop photo
+ * action result is received.
+ */
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != Activity.RESULT_OK) {
+ return false;
+ }
+ final Uri pictureUri = data != null && data.getData() != null
+ ? data.getData() : mTakePictureUri;
+
+ // Check if the result is a content uri
+ if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
+ Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
+ EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
+ return false;
+ }
+
+ switch (requestCode) {
+ case REQUEST_CODE_CROP_PHOTO:
+ mActivity.returnUriResult(pictureUri);
+ return true;
+ case REQUEST_CODE_TAKE_PHOTO:
+ case REQUEST_CODE_CHOOSE_PHOTO:
+ if (mTakePictureUri.equals(pictureUri)) {
+ if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
+ cropPhoto();
+ } else {
+ onPhotoNotCropped(pictureUri);
+ }
+ } else {
+ copyAndCropPhoto(pictureUri);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void takePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
+ appendOutputExtra(intent, mTakePictureUri);
+ mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ void choosePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
+ intent.setType("image/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ private void copyAndCropPhoto(final Uri pictureUri) {
+ // TODO: Replace AsyncTask
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ final ContentResolver cr = mActivity.getContentResolver();
+ try (InputStream in = cr.openInputStream(pictureUri);
+ OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy photo", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
+ cropPhoto();
+ }
+ }
+ }.execute();
+ }
+
+ private void cropPhoto() {
+ // TODO: Use a public intent, when there is one.
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(mTakePictureUri, "image/*");
+ appendOutputExtra(intent, mCropPictureUri);
+ appendCropExtras(intent);
+ if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
+ try {
+ StrictMode.disableDeathOnFileUriExposure();
+ mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
+ }
+ } else {
+ onPhotoNotCropped(mTakePictureUri);
+ }
+ }
+
+ private void appendOutputExtra(Intent intent, Uri pictureUri) {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+ }
+
+ private void appendCropExtras(Intent intent) {
+ intent.putExtra("crop", "true");
+ intent.putExtra("scale", true);
+ intent.putExtra("scaleUpIfNeeded", true);
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoSize);
+ intent.putExtra("outputY", mPhotoSize);
+ }
+
+ private void onPhotoNotCropped(final Uri data) {
+ // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ // Scale and crop to a square aspect ratio
+ Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(croppedImage);
+ Bitmap fullImage;
+ try {
+ InputStream imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ fullImage = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ return null;
+ }
+ if (fullImage != null) {
+ int rotation = getRotation(mActivity, data);
+ final int squareSize = Math.min(fullImage.getWidth(),
+ fullImage.getHeight());
+ final int left = (fullImage.getWidth() - squareSize) / 2;
+ final int top = (fullImage.getHeight() - squareSize) / 2;
+
+ Matrix matrix = new Matrix();
+ RectF rectSource = new RectF(left, top,
+ left + squareSize, top + squareSize);
+ RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
+ matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
+ matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
+ canvas.drawBitmap(fullImage, matrix, new Paint());
+ return croppedImage;
+ } else {
+ // Bah! Got nothin.
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
+ mActivity.returnUriResult(mCropPictureUri);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ /**
+ * Reads the image's exif data and determines the rotation degree needed to display the image
+ * in portrait mode.
+ */
+ private int getRotation(Context context, Uri selectedImage) {
+ int rotation = -1;
+ try {
+ InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+ ExifInterface exif = new ExifInterface(imageStream);
+ rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+ } catch (IOException exception) {
+ Log.e(TAG, "Error while getting rotation", exception);
+ }
+
+ switch (rotation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ private void saveBitmapToFile(Bitmap bitmap, File file) {
+ try {
+ OutputStream os = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ os.flush();
+ os.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot create temp file", e);
+ }
+ }
+
+ private static int getPhotoSize(Context context) {
+ try (Cursor cursor = context.getContentResolver().query(
+ ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
+ if (cursor != null) {
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ } else {
+ return DEFAULT_PHOTO_SIZE;
+ }
+ }
+ }
+
+ private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+ final File fullPath = new File(mImagesDir, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
new file mode 100644
index 0000000..50015e6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -0,0 +1,334 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity to allow the user to choose a user profile picture.
+ *
+ * <p>Options are provided to take a photo or choose a photo using the photo picker. In addition,
+ * preselected avatar images may be provided in the resource array {@code avatar_images}. If
+ * provided, every element of that array must be a bitmap drawable.
+ *
+ * <p>If preselected images are not provided, the default avatar will be shown instead, in a range
+ * of colors.
+ *
+ * <p>This activity should be started with startActivityForResult. If a photo or a preselected image
+ * is selected, a Uri will be returned in the data field of the result intent. If a colored default
+ * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR}
+ * and the data field will be empty.
+ */
+public class AvatarPickerActivity extends Activity {
+
+ static final String EXTRA_FILE_AUTHORITY = "file_authority";
+ static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color";
+
+ private static final String KEY_AWAITING_RESULT = "awaiting_result";
+ private static final String KEY_SELECTED_POSITION = "selected_position";
+
+ private boolean mWaitingForActivityResult;
+
+ private FooterButton mDoneButton;
+ private AvatarAdapter mAdapter;
+
+ private AvatarPhotoController mAvatarPhotoController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ThemeHelper.trySetDynamicColor(this);
+ setContentView(R.layout.avatar_picker);
+ setUpButtons();
+
+ RecyclerView recyclerView = findViewById(R.id.avatar_grid);
+ mAdapter = new AvatarAdapter();
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new GridLayoutManager(this,
+ getResources().getInteger(R.integer.avatar_picker_columns)));
+
+ restoreState(savedInstanceState);
+
+ mAvatarPhotoController = new AvatarPhotoController(
+ this, mWaitingForActivityResult, getFileAuthority());
+ }
+
+ private void setUpButtons() {
+ GlifLayout glifLayout = findViewById(R.id.glif_layout);
+ FooterBarMixin mixin = glifLayout.getMixin(FooterBarMixin.class);
+
+ FooterButton secondaryButton =
+ new FooterButton.Builder(this)
+ .setText("Cancel")
+ .setListener(view -> cancel())
+ .build();
+
+ mDoneButton =
+ new FooterButton.Builder(this)
+ .setText("Done")
+ .setListener(view -> mAdapter.returnSelectionResult())
+ .build();
+ mDoneButton.setEnabled(false);
+
+ mixin.setSecondaryButton(secondaryButton);
+ mixin.setPrimaryButton(mDoneButton);
+ }
+
+ private String getFileAuthority() {
+ String authority = getIntent().getStringExtra(EXTRA_FILE_AUTHORITY);
+ if (authority == null) {
+ throw new IllegalStateException("File authority must be provided");
+ }
+ return authority;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForActivityResult = false;
+ mAvatarPhotoController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+ outState.putInt(KEY_SELECTED_POSITION, mAdapter.mSelectedPosition);
+ super.onSaveInstanceState(outState);
+ }
+
+ private void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
+ mAdapter.mSelectedPosition =
+ savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE);
+ }
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ mWaitingForActivityResult = true;
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ void returnUriResult(Uri uri) {
+ Intent resultData = new Intent();
+ resultData.setData(uri);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ void returnColorResult(int color) {
+ Intent resultData = new Intent();
+ resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ private void cancel() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ private class AvatarAdapter extends RecyclerView.Adapter<AvatarViewHolder> {
+
+ private static final int NONE = -1;
+
+ private final int mTakePhotoPosition;
+ private final int mChoosePhotoPosition;
+ private final int mPreselectedImageStartPosition;
+
+ private final List<Drawable> mImageDrawables;
+ private final TypedArray mPreselectedImages;
+ private final int[] mUserIconColors;
+ private int mSelectedPosition = NONE;
+
+ AvatarAdapter() {
+ final boolean canTakePhoto =
+ PhotoCapabilityUtils.canTakePhoto(AvatarPickerActivity.this);
+ final boolean canChoosePhoto =
+ PhotoCapabilityUtils.canChoosePhoto(AvatarPickerActivity.this);
+ mTakePhotoPosition = (canTakePhoto ? 0 : NONE);
+ mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE);
+ mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0);
+
+ mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images);
+ mUserIconColors = UserIcons.getUserIconColors(getResources());
+ mImageDrawables = buildDrawableList();
+ }
+
+ @NonNull
+ @Override
+ public AvatarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+ LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ View itemView = layoutInflater.inflate(R.layout.avatar_item, parent, false);
+ return new AvatarViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AvatarViewHolder viewHolder, int position) {
+ if (position == mTakePhotoPosition) {
+ viewHolder.setDrawable(getDrawable(R.drawable.avatar_take_photo_circled));
+ viewHolder.setClickListener(view -> mAvatarPhotoController.takePhoto());
+
+ } else if (position == mChoosePhotoPosition) {
+ viewHolder.setDrawable(getDrawable(R.drawable.avatar_choose_photo_circled));
+ viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto());
+
+ } else if (position >= mPreselectedImageStartPosition) {
+ viewHolder.setSelected(position == mSelectedPosition);
+ viewHolder.setDrawable(mImageDrawables.get(indexFromPosition(position)));
+ viewHolder.setClickListener(view -> {
+ if (mSelectedPosition == position) {
+ deselect(position);
+ } else {
+ select(position);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mPreselectedImageStartPosition + mImageDrawables.size();
+ }
+
+ private List<Drawable> buildDrawableList() {
+ List<Drawable> result = new ArrayList<>();
+
+ for (int i = 0; i < mPreselectedImages.length(); i++) {
+ Drawable drawable = mPreselectedImages.getDrawable(i);
+ if (drawable instanceof BitmapDrawable) {
+ result.add(circularDrawableFrom((BitmapDrawable) drawable));
+ } else {
+ throw new IllegalStateException("Avatar drawables must be bitmaps");
+ }
+ }
+ if (!result.isEmpty()) {
+ return result;
+ }
+
+ // No preselected images. Use tinted default icon.
+ for (int i = 0; i < mUserIconColors.length; i++) {
+ result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i]));
+ }
+ return result;
+ }
+
+ private Drawable circularDrawableFrom(BitmapDrawable drawable) {
+ Bitmap bitmap = drawable.getBitmap();
+
+ RoundedBitmapDrawable roundedBitmapDrawable =
+ RoundedBitmapDrawableFactory.create(getResources(), bitmap);
+ roundedBitmapDrawable.setCircular(true);
+
+ return roundedBitmapDrawable;
+ }
+
+ private int indexFromPosition(int position) {
+ return position - mPreselectedImageStartPosition;
+ }
+
+ private void select(int position) {
+ final int oldSelection = mSelectedPosition;
+ mSelectedPosition = position;
+ notifyItemChanged(position);
+ if (oldSelection != NONE) {
+ notifyItemChanged(oldSelection);
+ } else {
+ mDoneButton.setEnabled(true);
+ }
+ }
+
+ private void deselect(int position) {
+ mSelectedPosition = NONE;
+ notifyItemChanged(position);
+ mDoneButton.setEnabled(false);
+ }
+
+ private void returnSelectionResult() {
+ int index = indexFromPosition(mSelectedPosition);
+ if (mPreselectedImages.length() > 0) {
+ int resourceId = mPreselectedImages.getResourceId(index, -1);
+ if (resourceId == -1) {
+ throw new IllegalStateException("Preselected avatar images must be resources.");
+ }
+ returnUriResult(uriForResourceId(resourceId));
+ } else {
+ returnColorResult(
+ mUserIconColors[index]);
+ }
+ }
+
+ private Uri uriForResourceId(int resourceId) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(getResources().getResourcePackageName(resourceId))
+ .appendPath(getResources().getResourceTypeName(resourceId))
+ .appendPath(getResources().getResourceEntryName(resourceId))
+ .build();
+ }
+ }
+
+ private static class AvatarViewHolder extends RecyclerView.ViewHolder {
+ private final ImageView mImageView;
+
+ AvatarViewHolder(View view) {
+ super(view);
+ mImageView = view.findViewById(R.id.avatar_image);
+ }
+
+ public void setDrawable(Drawable drawable) {
+ mImageView.setImageDrawable(drawable);
+ }
+
+ public void setClickListener(View.OnClickListener listener) {
+ mImageView.setOnClickListener(listener);
+ }
+
+ public void setSelected(boolean selected) {
+ mImageView.setSelected(selected);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 5859953..80ee86f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -36,6 +37,8 @@
import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
import java.io.File;
@@ -139,13 +142,20 @@
Drawable userIcon = getUserIcon(activity, defaultUserIcon);
userPhotoView.setImageDrawable(userIcon);
- if (canChangePhoto(activity)) {
- mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
- userPhotoView);
+ if (isChangePhotoRestrictedByBase(activity)) {
+ // some users can't change their photos so we need to remove the suggestive icon
+ content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
} else {
- // some users can't change their photos so we need to remove suggestive
- // background from the photoView
- userPhotoView.setBackground(null);
+ RestrictedLockUtils.EnforcedAdmin adminRestriction =
+ getChangePhotoAdminRestriction(activity);
+ if (adminRestriction != null) {
+ userPhotoView.setOnClickListener(view ->
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ activity, adminRestriction));
+ } else {
+ mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
+ userPhotoView);
+ }
}
mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
@@ -204,16 +214,21 @@
}
@VisibleForTesting
- boolean canChangePhoto(Context context) {
- return (PhotoCapabilityUtils.canCropPhoto(context)
- && PhotoCapabilityUtils.canChoosePhoto(context))
- || PhotoCapabilityUtils.canTakePhoto(context);
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+ return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
}
@VisibleForTesting
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mWaitingForActivityResult, mFileAuthority);
+ mSavedPhoto, mFileAuthority);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index f9584a3..f8bb38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,46 +16,21 @@
package com.android.settingslib.users;
+import android.annotation.NonNull;
import android.app.Activity;
-import android.content.ClipData;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.Intent;
-import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.StrictMode;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract.DisplayPhoto;
-import android.provider.MediaStore;
-import android.util.EventLog;
import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
import android.widget.ImageView;
-import android.widget.ListPopupWindow;
-import android.widget.TextView;
-import androidx.core.content.FileProvider;
-
+import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
-
-import libcore.io.Streams;
+import com.android.settingslib.utils.ThreadUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -63,8 +38,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
/**
* This class contains logic for starting activities to take/choose/crop photo, reads and transforms
@@ -75,45 +49,30 @@
// It seems that this class generates custom request codes and they may
// collide with ours, these values are very unlikely to have a conflict.
- private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
- private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
- private static final int REQUEST_CODE_CROP_PHOTO = 1003;
- // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
- // so we need a default photo size
- private static final int DEFAULT_PHOTO_SIZE = 500;
+ private static final int REQUEST_CODE_PICK_AVATAR = 1004;
private static final String IMAGES_DIR = "multi_user";
- private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
- private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
- private final int mPhotoSize;
-
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
private final ImageView mImageView;
private final String mFileAuthority;
private final File mImagesDir;
- private final Uri mCropPictureUri;
- private final Uri mTakePictureUri;
-
private Bitmap mNewUserPhotoBitmap;
private Drawable mNewUserPhotoDrawable;
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
- ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) {
+ ImageView view, Bitmap bitmap, String fileAuthority) {
mActivity = activity;
mActivityStarter = activityStarter;
- mImageView = view;
mFileAuthority = fileAuthority;
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
- mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
- mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
- mPhotoSize = getPhotoSize(activity);
- mImageView.setOnClickListener(v -> showUpdatePhotoPopup());
+ mImageView = view;
+ mImageView.setOnClickListener(v -> showAvatarPicker());
mNewUserPhotoBitmap = bitmap;
}
@@ -125,32 +84,19 @@
if (resultCode != Activity.RESULT_OK) {
return false;
}
- final Uri pictureUri = data != null && data.getData() != null
- ? data.getData() : mTakePictureUri;
- // Check if the result is a content uri
- if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
- Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
- EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
- return false;
- }
+ if (requestCode == REQUEST_CODE_PICK_AVATAR) {
+ if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) {
+ int tintColor =
+ data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1);
+ onDefaultIconSelected(tintColor);
+ return true;
+ }
+ if (data.getData() != null) {
+ onPhotoCropped(data.getData());
+ return true;
+ }
- switch (requestCode) {
- case REQUEST_CODE_CROP_PHOTO:
- onPhotoCropped(pictureUri);
- return true;
- case REQUEST_CODE_TAKE_PHOTO:
- case REQUEST_CODE_CHOOSE_PHOTO:
- if (mTakePictureUri.equals(pictureUri)) {
- if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
- cropPhoto();
- } else {
- onPhotoNotCropped(pictureUri);
- }
- } else {
- copyAndCropPhoto(pictureUri);
- }
- return true;
}
return false;
}
@@ -159,224 +105,60 @@
return mNewUserPhotoDrawable;
}
- private void showUpdatePhotoPopup() {
- final Context context = mImageView.getContext();
- final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
- final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
-
- if (!canTakePhoto && !canChoosePhoto) {
- return;
- }
-
- final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
-
- if (canTakePhoto) {
- final String title = context.getString(R.string.user_image_take_photo);
- items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
- this::takePhoto));
- }
-
- if (canChoosePhoto) {
- final String title = context.getString(R.string.user_image_choose_photo);
- items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
- this::choosePhoto));
- }
-
- final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
-
- listPopupWindow.setAnchorView(mImageView);
- listPopupWindow.setModal(true);
- listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
- listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
-
- final int width = Math.max(mImageView.getWidth(), context.getResources()
- .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
- listPopupWindow.setWidth(width);
- listPopupWindow.setDropDownGravity(Gravity.START);
-
- listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
- listPopupWindow.dismiss();
- final RestrictedMenuItem item =
- (RestrictedMenuItem) parent.getAdapter().getItem(position);
- item.doAction();
- });
-
- listPopupWindow.show();
+ private void showAvatarPicker() {
+ Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
+ mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
- private void takePhoto() {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
- appendOutputExtra(intent, mTakePictureUri);
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
- }
+ private void onDefaultIconSelected(int tintColor) {
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Drawable drawable =
+ UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
+ Bitmap bitmap = convertToBitmap(drawable,
+ (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
- private void choosePhoto() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- intent.setType("image/*");
- appendOutputExtra(intent, mTakePictureUri);
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
- }
-
- private void copyAndCropPhoto(final Uri pictureUri) {
- // TODO: Replace AsyncTask
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- final ContentResolver cr = mActivity.getContentResolver();
- try (InputStream in = cr.openInputStream(pictureUri);
- OutputStream out = cr.openOutputStream(mTakePictureUri)) {
- Streams.copy(in, out);
- } catch (IOException e) {
- Log.w(TAG, "Failed to copy photo", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
- cropPhoto();
- }
- }
- }.execute();
- }
-
- private void cropPhoto() {
- // TODO: Use a public intent, when there is one.
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(mTakePictureUri, "image/*");
- appendOutputExtra(intent, mCropPictureUri);
- appendCropExtras(intent);
- if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
- try {
- StrictMode.disableDeathOnFileUriExposure();
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
- } else {
- onPhotoNotCropped(mTakePictureUri);
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error processing default icon", e);
}
}
- private void appendOutputExtra(Intent intent, Uri pictureUri) {
- intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
- }
-
- private void appendCropExtras(Intent intent) {
- intent.putExtra("crop", "true");
- intent.putExtra("scale", true);
- intent.putExtra("scaleUpIfNeeded", true);
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", mPhotoSize);
- intent.putExtra("outputY", mPhotoSize);
+ private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, size, size);
+ icon.draw(canvas);
+ return bitmap;
}
private void onPhotoCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- InputStream imageStream = null;
- try {
- imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- return BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- Log.w(TAG, "Cannot find image file", fe);
- return null;
- } finally {
- if (imageStream != null) {
- try {
- imageStream.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Cannot close image stream", ioe);
- }
+ ThreadUtils.postOnBackgroundThread(() -> {
+ InputStream imageStream = null;
+ Bitmap bitmap = null;
+ try {
+ imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ bitmap = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ Log.w(TAG, "Cannot find image file", fe);
+ } finally {
+ if (imageStream != null) {
+ try {
+ imageStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Cannot close image stream", ioe);
}
}
}
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
-
+ if (bitmap != null) {
+ Bitmap finalBitmap = bitmap;
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap));
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- private void onPhotoNotCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- // Scale and crop to a square aspect ratio
- Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
- Config.ARGB_8888);
- Canvas canvas = new Canvas(croppedImage);
- Bitmap fullImage;
- try {
- InputStream imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- fullImage = BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- return null;
- }
- if (fullImage != null) {
- int rotation = getRotation(mActivity, data);
- final int squareSize = Math.min(fullImage.getWidth(),
- fullImage.getHeight());
- final int left = (fullImage.getWidth() - squareSize) / 2;
- final int top = (fullImage.getHeight() - squareSize) / 2;
-
- Matrix matrix = new Matrix();
- RectF rectSource = new RectF(left, top,
- left + squareSize, top + squareSize);
- RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
- matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
- matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
- canvas.drawBitmap(fullImage, matrix, new Paint());
- return croppedImage;
- } else {
- // Bah! Got nothin.
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- /**
- * Reads the image's exif data and determines the rotation degree needed to display the image
- * in portrait mode.
- */
- private int getRotation(Context context, Uri selectedImage) {
- int rotation = -1;
- try {
- InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
- ExifInterface exif = new ExifInterface(imageStream);
- rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
- } catch (IOException exception) {
- Log.e(TAG, "Error while getting rotation", exception);
- }
-
- switch (rotation) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- return 90;
- case ExifInterface.ORIENTATION_ROTATE_180:
- return 180;
- case ExifInterface.ORIENTATION_ROTATE_270:
- return 270;
- default:
- return 0;
- }
+ });
}
private void onPhotoProcessed(Bitmap bitmap) {
@@ -386,29 +168,6 @@
.getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
mImageView.setImageDrawable(mNewUserPhotoDrawable);
}
- new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete();
- new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete();
- }
-
- private static int getPhotoSize(Context context) {
- try (Cursor cursor = context.getContentResolver().query(
- DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
- if (cursor != null) {
- cursor.moveToFirst();
- return cursor.getInt(0);
- } else {
- return DEFAULT_PHOTO_SIZE;
- }
- }
- }
-
- private Uri createTempImageUri(Context context, String fileName, boolean purge) {
- final File fullPath = new File(mImagesDir, fileName);
- if (purge) {
- fullPath.delete();
- }
- return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
}
File saveNewUserPhotoBitmap() {
@@ -435,84 +194,4 @@
void removeNewUserPhotoBitmapFile() {
new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
}
-
- private static final class RestrictedMenuItem {
- private final Context mContext;
- private final String mTitle;
- private final Runnable mAction;
- private final RestrictedLockUtils.EnforcedAdmin mAdmin;
- // Restriction may be set by system or something else via UserManager.setUserRestriction().
- private final boolean mIsRestrictedByBase;
-
- /**
- * The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
- *
- * @param context A context.
- * @param title The title of the menu item.
- * @param restriction The restriction, that if is set, blocks the menu item.
- * @param action The action on menu item click.
- */
- RestrictedMenuItem(Context context, String title, String restriction,
- Runnable action) {
- mContext = context;
- mTitle = title;
- mAction = action;
-
- final int myUserId = UserHandle.myUserId();
- mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
- restriction, myUserId);
- mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- restriction, myUserId);
- }
-
- @Override
- public String toString() {
- return mTitle;
- }
-
- void doAction() {
- if (isRestrictedByBase()) {
- return;
- }
-
- if (isRestrictedByAdmin()) {
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
- return;
- }
-
- mAction.run();
- }
-
- boolean isRestrictedByAdmin() {
- return mAdmin != null;
- }
-
- boolean isRestrictedByBase() {
- return mIsRestrictedByBase;
- }
- }
-
- /**
- * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
- * any element can be restricted by admin (profile owner or device owner).
- */
- private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
- RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
- super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view = super.getView(position, convertView, parent);
- final RestrictedMenuItem item = getItem(position);
- final TextView text = (TextView) view.findViewById(R.id.text);
- final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
-
- text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
- image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase()
- ? ImageView.VISIBLE : ImageView.GONE);
-
- return view;
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
index 165c280..b8615a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
@@ -40,12 +40,12 @@
/**
* Check if the current user can perform any activity for
- * android.intent.action.GET_CONTENT action for images.
+ * ACTION_PICK_IMAGES action for images.
* Returns false if the device is currently locked and
* requires a PIN, pattern or password to unlock.
*/
public static boolean canChoosePhoto(Context context) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.setType("image/*");
boolean canPerformActivityForGetImage =
context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
new file mode 100644
index 0000000..64f8bef
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.graphics.drawable.Drawable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppIconCacheManagerTest {
+
+ private static final String APP_PACKAGE_NAME = "com.test.app";
+ private static final int APP_UID = 9999;
+
+ @Mock
+ private Drawable mIcon;
+
+ private AppIconCacheManager mAppIconCacheManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mAppIconCacheManager = AppIconCacheManager.getInstance();
+ doReturn(10).when(mIcon).getIntrinsicHeight();
+ doReturn(10).when(mIcon).getIntrinsicWidth();
+ doReturn(mIcon).when(mIcon).mutate();
+ }
+
+ @After
+ public void tearDown() {
+ AppIconCacheManager.release();
+ }
+
+ @Test
+ public void get_invalidPackageOrUid_shouldReturnNull() {
+ assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull();
+ }
+
+ @Test
+ public void put_invalidPackageOrUid_shouldNotCrash() {
+ mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon);
+ // no crash
+ }
+
+ @Test
+ public void put_invalidIcon_shouldNotCacheIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+
+ @Test
+ public void put_invalidIconSize_shouldNotCacheIcon() {
+ doReturn(-1).when(mIcon).getIntrinsicHeight();
+ doReturn(-1).when(mIcon).getIntrinsicWidth();
+
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+
+ @Test
+ public void put_shouldCacheIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon);
+ }
+
+ @Test
+ public void release_noInstance_shouldNotCrash() {
+ mAppIconCacheManager = null;
+
+ AppIconCacheManager.release();
+ // no crash
+ }
+
+ @Test
+ public void release_existInstance_shouldClearCache() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ AppIconCacheManager.release();
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
new file mode 100644
index 0000000..8e448aa
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
+
+import com.android.settingslib.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppUtilsTest {
+
+ private static final String APP_PACKAGE_NAME = "com.test.app";
+ private static final int APP_UID = 9999;
+
+ @Mock
+ private Drawable mIcon;
+
+ private Context mContext;
+ private AppIconCacheManager mAppIconCacheManager;
+ private ApplicationInfo mAppInfo;
+ private ApplicationsState.AppEntry mAppEntry;
+ private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mAppIconCacheManager = AppIconCacheManager.getInstance();
+ mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID);
+ mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
+ mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
+ doReturn(mIcon).when(mIcon).mutate();
+ }
+
+ @After
+ public void tearDown() {
+ AppIconCacheManager.release();
+ }
+
+ @Test
+ public void getIcon_nullAppEntry_shouldReturnNull() {
+ assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull();
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void getIcon_noCachedIcon_shouldNotReturnNull() {
+ assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull();
+ }
+
+ @Test
+ public void getIcon_existCachedIcon_shouldReturnCachedIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon);
+ }
+
+ @Test
+ public void getIconFromCache_nullAppEntry_shouldReturnNull() {
+ assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull();
+ }
+
+ @Test
+ public void getIconFromCache_shouldReturnCachedIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon);
+ }
+
+ @Test
+ public void preloadTopIcons_nullAppEntries_shouldNotCrash() {
+ AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1);
+ // no crash
+ }
+
+ @Test
+ public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() {
+ AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException {
+ AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1);
+
+ TimeUnit.SECONDS.sleep(1);
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
+ }
+
+ private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
+ ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
+ appEntry.label = "label";
+ appEntry.mounted = true;
+ final File apkFile = mock(File.class);
+ doReturn(true).when(apkFile).exists();
+ try {
+ Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile");
+ field.setAccessible(true);
+ field.set(appEntry, apkFile);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ fail("Not able to mock apkFile: " + e);
+ }
+ return appEntry;
+ }
+
+ private ApplicationInfo createApplicationInfo(String packageName, int uid) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.sourceDir = "appPath";
+ appInfo.packageName = packageName;
+ appInfo.uid = uid;
+ return appInfo;
+ }
+
+ @Implements(Utils.class)
+ private static class ShadowUtils {
+ @Implementation
+ public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
+ final Drawable icon = mock(Drawable.class);
+ doReturn(10).when(icon).getIntrinsicHeight();
+ doReturn(10).when(icon).getIntrinsicWidth();
+ doReturn(icon).when(icon).mutate();
+ return icon;
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 10ccd22..1f2297b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -33,6 +33,7 @@
import static org.robolectric.shadow.api.Shadow.extract;
import android.annotation.UserIdInt;
+import android.app.Application;
import android.app.ApplicationPackageManager;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
@@ -110,6 +111,7 @@
private ApplicationsState mApplicationsState;
private Session mSession;
+ private Application mApplication;
@Mock
private Callbacks mCallbacks;
@@ -190,6 +192,7 @@
ShadowContextImpl shadowContext = Shadow.extract(
RuntimeEnvironment.application.getBaseContext());
shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
+ mApplication = spy(RuntimeEnvironment.application);
StorageStats storageStats = new StorageStats();
storageStats.codeBytes = 10;
storageStats.cacheBytes = 30;
@@ -207,8 +210,7 @@
anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
ApplicationsState.sInstance = null;
- mApplicationsState =
- ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
+ mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
mApplicationsState.clearEntries();
mSession = mApplicationsState.newSession(mCallbacks);
@@ -703,6 +705,23 @@
verify(mApplicationsState, never()).clearEntries();
}
+ @Test
+ public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() {
+ when(mApplication.getPackageName()).thenReturn("com.android.settings");
+ mSession.onResume();
+
+ addApp(HOME_PACKAGE_NAME, 1);
+ addApp(LAUNCHABLE_PACKAGE_NAME, 2);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ processAllMessages();
+ verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
+
+ List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
+ for (AppEntry appEntry : appEntries) {
+ assertThat(appEntry.icon).isNull();
+ }
+ }
+
private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps,
ArrayList<ApplicationInfo> profileApps)
throws RemoteException {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
index c1cc3ae..445701f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doReturn;
@@ -33,7 +34,6 @@
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -55,7 +55,6 @@
}
@Test
- @Ignore
public void testBroadcastReceiver() {
final AbstractConnectivityPreferenceController preferenceController =
spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle));
@@ -73,7 +72,7 @@
verify(mContext, times(1))
.registerReceiver(receiverArgumentCaptor.capture(),
filterArgumentCaptor.capture(),
- anyString(), nullable(Handler.class));
+ anyString(), nullable(Handler.class), anyInt());
final BroadcastReceiver receiver = receiverArgumentCaptor.getValue();
final IntentFilter filter = filterArgumentCaptor.getValue();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index d6c8816..a5ee4c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -62,7 +62,7 @@
@Mock
private ActivityStarter mActivityStarter;
- private boolean mCanChangePhoto;
+ private boolean mPhotoRestrictedByBase;
private Activity mActivity;
private TestEditUserInfoController mController;
@@ -85,8 +85,8 @@
}
@Override
- boolean canChangePhoto(Context context) {
- return mCanChangePhoto;
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return mPhotoRestrictedByBase;
}
}
@@ -96,7 +96,7 @@
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
mController = new TestEditUserInfoController();
- mCanChangePhoto = true;
+ mPhotoRestrictedByBase = true;
}
@Test
@@ -260,7 +260,7 @@
@Test
public void createDialog_canNotChangePhoto_nullPhotoController() {
- mCanChangePhoto = false;
+ mPhotoRestrictedByBase = false;
mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
"test", "title", null, null);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 52a708d..13ae870 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -326,6 +326,8 @@
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
Settings.Global.LOW_POWER_MODE_STICKY,
Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+ Settings.Global.LOW_POWER_STANDBY_ENABLED,
Settings.Global.LTE_SERVICE_FORCED,
Settings.Global.LID_BEHAVIOR,
Settings.Global.MAX_ERROR_BYTES_PREFIX,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ebff00c..27fc6ba 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -248,6 +248,7 @@
<uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
<uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
<uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
+ <uses-permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY" />
<uses-permission android:name="android.permission.MANAGE_SEARCH_UI" />
<uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
<uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 7fd029c..11a5665 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -35,7 +35,8 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="vertical"
- android:clipToPadding="false" />
+ android:clipToPadding="false"
+ android:paddingHorizontal="@dimen/controls_padding_horizontal" />
</com.android.systemui.globalactions.MinHeightScrollView>
</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index ac4dfd2..8c5006d 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -31,9 +31,6 @@
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">1</integer>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">4</integer>
-
<!-- Max number of columns for power menu -->
<integer name="power_menu_max_columns">4</integer>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index dabc310..fe546f6 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,9 +15,6 @@
~ limitations under the License
-->
<resources>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">2</integer>
-
<!-- The maximum number of rows in the QSPanel -->
<integer name="quick_settings_max_rows">3</integer>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 02fd25b..3c6a81e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -15,7 +15,6 @@
~ limitations under the License
-->
<resources>
-
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">6</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f5dc7e3e..1b8453a 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -29,9 +29,6 @@
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">0</integer>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">4</integer>
-
<!-- How many lines to show in the security footer -->
<integer name="qs_security_footer_maxLines">1</integer>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index ae89ef4..be34a48 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -15,9 +15,6 @@
~ limitations under the License
-->
<resources>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">2</integer>
-
<!-- The maximum number of rows in the QSPanel -->
<integer name="quick_settings_max_rows">3</integer>
diff --git a/packages/SystemUI/res/values-w500dp/config.xml b/packages/SystemUI/res/values-w500dp/config.xml
new file mode 100644
index 0000000..ef499ff
--- /dev/null
+++ b/packages/SystemUI/res/values-w500dp/config.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">3</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-w500dp/dimens.xml b/packages/SystemUI/res/values-w500dp/dimens.xml
new file mode 100644
index 0000000..5ce5cee
--- /dev/null
+++ b/packages/SystemUI/res/values-w500dp/dimens.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<resources>
+ <dimen name="controls_padding_horizontal">75dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-w850dp/config.xml b/packages/SystemUI/res/values-w850dp/config.xml
new file mode 100644
index 0000000..337ebe1
--- /dev/null
+++ b/packages/SystemUI/res/values-w850dp/config.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">4</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-w850dp/dimens.xml b/packages/SystemUI/res/values-w850dp/dimens.xml
new file mode 100644
index 0000000..bb6ba8fb
--- /dev/null
+++ b/packages/SystemUI/res/values-w850dp/dimens.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<resources>
+ <dimen name="controls_padding_horizontal">205dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f94031c..8b88cdc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -369,6 +369,13 @@
<!-- The top margin of the panel that holds the list of notifications. -->
<dimen name="notification_panel_margin_top">0dp</dimen>
+ <!-- The minimum content height for the split shade NSSL.
+ It is used because if the height is too small, the expansion motion is too fast.
+ Note that the value of 256dp is more or less a random value and can be changed to tweak
+ the expansion motion.
+ -->
+ <dimen name="nssl_split_shade_min_content_height">256dp</dimen>
+
<!-- The bottom margin of the panel that holds the list of notifications. -->
<dimen name="notification_panel_margin_bottom">0dp</dimen>
@@ -1028,6 +1035,7 @@
<dimen name="controls_header_bottom_margin">24dp</dimen>
<dimen name="controls_header_app_icon_size">24dp</dimen>
<dimen name="controls_top_margin">48dp</dimen>
+ <dimen name="controls_padding_horizontal">0dp</dimen>
<dimen name="control_header_text_size">20sp</dimen>
<dimen name="control_item_text_size">16sp</dimen>
<dimen name="control_menu_item_text_size">16sp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 5d092d0..eebc791 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -120,6 +120,8 @@
public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
// The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
+ // The current app is in immersive mode
+ public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -145,7 +147,8 @@
SYSUI_STATE_IME_SWITCHER_SHOWING,
SYSUI_STATE_DEVICE_DOZING,
SYSUI_STATE_BACK_DISABLED,
- SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
+ SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ SYSUI_STATE_IMMERSIVE_MODE
})
public @interface SystemUiStateFlags {}
@@ -179,6 +182,7 @@
str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
? "bubbles_mange_menu_expanded" : "");
+ str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
return str.toString();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 6626f59..80a3a0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,7 +35,6 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Process;
import android.os.VibrationAttributes;
-import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
@@ -60,6 +59,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -103,7 +103,7 @@
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
- @Nullable private final Vibrator mVibrator;
+ @NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
@@ -154,7 +154,7 @@
@NonNull AccessibilityManager accessibilityManager,
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
- @Nullable Vibrator vibrator,
+ @NonNull VibratorHelper vibrator,
@Nullable AuthRippleController authRippleController,
@NonNull @Main Resources resources
) {
@@ -560,7 +560,7 @@
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
- if (mVibrator != null && !mDownDetected) {
+ if (!mDownDetected) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
@@ -647,15 +647,13 @@
mOnGestureDetectedRunnable.run();
}
- if (mVibrator != null) {
- // play device entry haptic (same as biometric success haptic)
- mVibrator.vibrate(
- Process.myUid(),
- getContext().getOpPackageName(),
- UdfpsController.EFFECT_CLICK,
- "lock-icon-device-entry",
- TOUCH_VIBRATION_ATTRIBUTES);
- }
+ // play device entry haptic (same as biometric success haptic)
+ mVibrator.vibrate(
+ Process.myUid(),
+ getContext().getOpPackageName(),
+ UdfpsController.EFFECT_CLICK,
+ "lock-icon-device-entry",
+ TOUCH_VIBRATION_ATTRIBUTES);
mKeyguardViewController.showBouncer(/* scrim */ true);
}
@@ -670,12 +668,9 @@
mVelocityTracker.recycle();
mVelocityTracker = null;
}
- if (mVibrator != null) {
- mVibrator.cancel();
- }
+ mVibrator.cancel();
}
-
private boolean inLockIconArea(MotionEvent event) {
return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& mView.getVisibility() == View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5ddfd75..8052c20 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -42,7 +42,6 @@
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -64,6 +63,7 @@
import com.android.systemui.plugins.FalsingManager;
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.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -117,7 +117,7 @@
@NonNull private final DumpManager mDumpManager;
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Nullable private final Vibrator mVibrator;
+ @NonNull private final VibratorHelper mVibrator;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -506,7 +506,7 @@
@NonNull AccessibilityManager accessibilityManager,
@NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController,
@NonNull ScreenLifecycle screenLifecycle,
- @Nullable Vibrator vibrator,
+ @NonNull VibratorHelper vibrator,
@NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
@NonNull Optional<UdfpsHbmProvider> hbmProvider,
@NonNull KeyguardStateController keyguardStateController,
@@ -577,14 +577,12 @@
*/
@VisibleForTesting
public void playStartHaptic() {
- if (mVibrator != null) {
- mVibrator.vibrate(
- Process.myUid(),
- mContext.getOpPackageName(),
- EFFECT_CLICK,
- "udfps-onStart-click",
- VIBRATION_ATTRIBUTES);
- }
+ mVibrator.vibrate(
+ Process.myUid(),
+ mContext.getOpPackageName(),
+ EFFECT_CLICK,
+ "udfps-onStart-click",
+ VIBRATION_ATTRIBUTES);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index e231310..eaee19a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -18,16 +18,12 @@
import android.media.AudioAttributes
import android.os.VibrationEffect
-import android.os.Vibrator
-
import com.android.keyguard.KeyguardUpdateMonitor
-
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
-
import java.io.PrintWriter
-
import javax.inject.Inject
/**
@@ -36,7 +32,7 @@
@SysUISingleton
class UdfpsHapticsSimulator @Inject constructor(
commandRegistry: CommandRegistry,
- val vibrator: Vibrator?,
+ val vibrator: VibratorHelper,
val keyguardUpdateMonitor: KeyguardUpdateMonitor
) : Command {
val sonificationEffects =
@@ -60,13 +56,13 @@
}
"success" -> {
// needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
- vibrator?.vibrate(
+ vibrator.vibrate(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
sonificationEffects)
}
"error" -> {
// needs to be kept up to date with AcquisitionClient#ERROR_VIBRATION_EFFECT
- vibrator?.vibrate(
+ vibrator.vibrate(
VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
sonificationEffects)
}
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 a29f3e9..f87fa96 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -24,7 +24,6 @@
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.VibrationEffect
-import android.os.Vibrator
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
@@ -32,16 +31,14 @@
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.TaskViewFactory
-import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -52,14 +49,11 @@
@Main private val uiExecutor: DelayableExecutor,
private val activityStarter: ActivityStarter,
private val keyguardStateController: KeyguardStateController,
- private val globalActionsComponent: GlobalActionsComponent,
private val taskViewFactory: Optional<TaskViewFactory>,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val lazyUiController: Lazy<ControlsUiController>,
- private val controlsMetricsLogger: ControlsMetricsLogger
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val vibrator: VibratorHelper
) : ControlActionCoordinator {
private var dialog: Dialog? = null
- private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
private val isLocked: Boolean
@@ -194,7 +188,7 @@
}
private fun vibrate(effect: VibrationEffect) {
- bgExecutor.execute { vibrator.vibrate(effect) }
+ vibrator.vibrate(effect)
}
private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 2ebcd853..f0371fc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -62,7 +62,6 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
@@ -119,6 +118,7 @@
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -327,7 +327,7 @@
TelephonyListenerManager telephonyListenerManager,
GlobalSettings globalSettings,
SecureSettings secureSettings,
- @Nullable Vibrator vibrator,
+ @NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
@@ -397,7 +397,7 @@
mGlobalSettings.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
- mHasVibrator = vibrator != null && vibrator.hasVibrator();
+ mHasVibrator = vibrator.hasVibrator();
mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
R.bool.config_useFixedVolume);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 701d139..5bf569a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -138,7 +138,7 @@
* state of the keyguard, power management events that effect whether the keyguard
* should be shown or reset, callbacks to the phone window manager to notify
* it of when the keyguard is showing, and events from the keyguard view itself
- * stating that the keyguard was succesfully unlocked.
+ * stating that the keyguard was successfully unlocked.
*
* Note that the keyguard view is shown when the screen is off (as appropriate)
* so that once the screen comes on, it will be ready immediately.
@@ -152,15 +152,15 @@
* - the keyguard is showing
*
* Example external events that translate to keyguard view changes:
- * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * - screen turned off -> reset the keyguard, and show it, so it will be ready
* next time the screen turns on
* - keyboard is slid open -> if the keyguard is not secure, hide it
*
* Events from the keyguard view:
- * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * - user successfully unlocked keyguard -> hide keyguard view, and no longer
* restrict input events.
*
- * Note: in addition to normal power managment events that effect the state of
+ * Note: in addition to normal power management events that effect the state of
* whether the keyguard should be showing, external apps and services may request
* that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When
* false, this will override all other conditions for turning on the keyguard.
@@ -224,7 +224,7 @@
/**
* How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
* callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
- * that is reenabling the keyguard.
+ * that is re-enabling the keyguard.
*/
private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
@@ -273,14 +273,14 @@
// these are protected by synchronized (this)
/**
- * External apps (like the phone app) can tell us to disable the keygaurd.
+ * External apps (like the phone app) can tell us to disable the keyguard.
*/
private boolean mExternallyEnabled = true;
/**
* Remember if an external call to {@link #setKeyguardEnabled} with value
* false caused us to hide the keyguard, so that we need to reshow it once
- * the keygaurd is reenabled with another call with value true.
+ * the keyguard is re-enabled with another call with value true.
*/
private boolean mNeedToReshowWhenReenabled = false;
@@ -304,7 +304,7 @@
private int mDelayedShowingSequence;
/**
- * Simiar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -341,7 +341,7 @@
private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
- * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be
+ * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
* called.
* */
private boolean mHiding;
@@ -355,7 +355,7 @@
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
/**
- * {@link #setKeyguardEnabled} waits on this condition when it reenables
+ * {@link #setKeyguardEnabled} waits on this condition when it re-enables
* the keyguard.
*/
private boolean mWaitingUntilKeyguardVisible = false;
@@ -494,7 +494,7 @@
synchronized (KeyguardViewMediator.this) {
resetKeyguardDonePendingLocked();
if (mLockPatternUtils.isLockScreenDisabled(userId)) {
- // If we switching to a user that has keyguard disabled, dismiss keyguard.
+ // If we are switching to a user that has keyguard disabled, dismiss keyguard.
dismiss(null /* callback */, null /* message */);
} else {
resetStateLocked();
@@ -508,7 +508,7 @@
if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
if (userId != UserHandle.USER_SYSTEM) {
UserInfo info = UserManager.get(mContext).getUserInfo(userId);
- // Don't try to dismiss if the user has Pin/Patter/Password set
+ // Don't try to dismiss if the user has Pin/Pattern/Password set
if (info == null || mLockPatternUtils.isSecure(userId)) {
return;
} else if (info.isGuest() || info.isDemo()) {
@@ -1041,7 +1041,7 @@
mUpdateMonitor.dispatchStartedGoingToSleep(offReason);
- // Reset keyguard going away state so we can start listening for fingerprint. We
+ // Reset keyguard going away state, so we can start listening for fingerprint. We
// explicitly DO NOT want to call
// mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
// here, since that will mess with the device lock state.
@@ -1121,9 +1121,9 @@
}
private long getLockTimeout(int userId) {
- // if the screen turned off because of timeout or the user hit the power button
+ // if the screen turned off because of timeout or the user hit the power button,
// and we don't need to lock immediately, set an alarm
- // to enable it a little bit later (i.e, give the user a chance
+ // to enable it a bit later (i.e, give the user a chance
// to turn the screen back on within a certain window without
// having to unlock the screen)
final ContentResolver cr = mContext.getContentResolver();
@@ -1217,7 +1217,7 @@
}
/**
- * Let's us know when the device is waking up.
+ * It will let us know when the device is waking up.
*/
public void onStartedWakingUp(boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
@@ -1299,7 +1299,7 @@
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
// we're in the process of handling a request to verify the user
- // can get past the keyguard. ignore extraneous requests to disable / reenable
+ // can get past the keyguard. ignore extraneous requests to disable / re-enable
return;
}
@@ -1310,7 +1310,7 @@
updateInputRestrictedLocked();
hideLocked();
} else if (enabled && mNeedToReshowWhenReenabled) {
- // reenabled after previously hidden, reshow
+ // re-enabled after previously hidden, reshow
if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ "status bar expansion");
mNeedToReshowWhenReenabled = false;
@@ -1328,8 +1328,8 @@
} else {
showLocked(null);
- // block until we know the keygaurd is done drawing (and post a message
- // to unblock us after a timeout so we don't risk blocking too long
+ // block until we know the keyguard is done drawing (and post a message
+ // to unblock us after a timeout, so we don't risk blocking too long
// and causing an ANR).
mWaitingUntilKeyguardVisible = true;
mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
@@ -1917,7 +1917,7 @@
mExitSecureCallback = null;
- // after succesfully exiting securely, no need to reshow
+ // after successfully exiting securely, no need to reshow
// the keyguard when they've released the lock
mExternallyEnabled = true;
mNeedToReshowWhenReenabled = false;
@@ -1992,7 +1992,7 @@
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
int id = mLockSounds.play(soundId,
- mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ mLockSoundVolume, mLockSoundVolume, 1/*priority*/, 0/*loop*/, 1.0f/*rate*/);
synchronized (this) {
mLockSoundStreamId = id;
}
@@ -2078,7 +2078,7 @@
|| mScreenOnCoordinator.getWakeAndUnlocking()
&& mWallpaperSupportsAmbientMode) {
// When the wallpaper supports ambient mode, the scrim isn't fully opaque during
- // wake and unlock and we should fade in the app on top of the wallpaper
+ // wake and unlock, and we should fade in the app on top of the wallpaper
flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2167,15 +2167,15 @@
synchronized (KeyguardViewMediator.this) {
// Tell ActivityManager that we canceled the keyguard animation if
- // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless
- // we're animating the surface behind the keyguard and will be hiding the keyguard
- // shortly.
+ // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
+ // unless we're animating the surface behind the keyguard and will be hiding the
+ // keyguard shortly.
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
if (finishedCallback != null) {
// There will not execute animation, send a finish callback to ensure the remote
- // animation won't hanging there.
+ // animation won't hang there.
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
@@ -2192,7 +2192,7 @@
if (mScreenOnCoordinator.getWakeAndUnlocking()) {
// Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
- // the next draw from here so we don't have to wait for window manager to signal
+ // the next draw from here, so we don't have to wait for window manager to signal
// this to our ViewRootImpl.
mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
mScreenOnCoordinator.setWakeAndUnlocking(false);
@@ -2344,7 +2344,7 @@
}
/**
- * Called if the keyguard exit animation has been cancelled and we should dismiss to the
+ * Called if the keyguard exit animation has been cancelled, and we should dismiss to the
* keyguard.
*
* This can happen due to the system cancelling the RemoteAnimation (due to a timeout, a new
@@ -2595,7 +2595,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and WindowManager has started running keyguard exit
* animation.
*
@@ -2609,7 +2609,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and System UI should start running keyguard exit animation.
*
* @param apps The list of apps to animate.
@@ -2625,7 +2625,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and start running keyguard exit animation.
*
* @param startTime the start time of the animation in uptime milliseconds. Deprecated.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 441e79a..ec15b24 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -31,6 +31,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -304,6 +305,7 @@
allowSystemGestureIgnoringBarVisibility())
.setFlag(SYSUI_STATE_SCREEN_PINNING,
ActivityManagerWrapper.getInstance().isScreenPinningActive())
+ .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode())
.commitUpdate(mDisplayId);
}
@@ -445,6 +447,10 @@
return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
+ private boolean isImmersiveMode() {
+ return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ }
+
@Override
public void onConfigurationChanged(Configuration configuration) {
mEdgeBackGestureHandler.onConfigurationChanged(configuration);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c136d9c..b312ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -337,11 +337,11 @@
if (field != value || forceApplyAmount) {
field = value
if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
- nsslController.setTransitionToFullShadeAmount(field)
+ qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+ nsslController.setTransitionToFullShadeAmount(field, qSDragProgress)
+ qS.setTransitionToFullShadeAmount(field, qSDragProgress)
notificationPanelController.setTransitionToFullShadeAmount(field,
false /* animate */, 0 /* delay */)
- qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
- qS.setTransitionToFullShadeAmount(field, qSDragProgress)
// TODO: appear media also in split shade
val mediaAmount = if (useSplitShade) 0f else field
mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 51a66aa..3411eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -32,6 +34,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -45,6 +48,7 @@
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.Utils;
/**
* A notification shelf view that is placed inside the notification scroller. It manages the
@@ -81,6 +85,11 @@
private int mIndexOfFirstViewInShelf = -1;
private float mCornerAnimationDistance;
private NotificationShelfController mController;
+ private int mActualWidth = -1;
+ private boolean mUseSplitShade;
+
+ /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
+ private float mFractionToShade;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -122,13 +131,16 @@
layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
setLayoutParams(layoutParams);
- int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
+ final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ // TODO(b/213480466) enable short shelf on split shade
+ mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources());
+
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
setVisibility(GONE);
@@ -203,6 +215,10 @@
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
viewState.yTranslation = stackEnd - viewState.height;
+
+ final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN);
+ final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade);
+ updateStateWidth(viewState, fraction, shortestWidth);
} else {
viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -211,6 +227,77 @@
}
/**
+ * @param shelfState View state for NotificationShelf
+ * @param fraction Fraction of lockscreen to shade transition
+ * @param shortestWidth Shortest width to use for lockscreen shelf
+ */
+ @VisibleForTesting
+ public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) {
+ shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard()
+ ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction)
+ : getWidth();
+ }
+
+ /**
+ * @param fractionToShade Fraction of lockscreen to shade transition
+ */
+ public void setFractionToShade(float fractionToShade) {
+ mFractionToShade = fractionToShade;
+ }
+
+ /**
+ * @return Actual width of shelf, accounting for possible ongoing width animation
+ */
+ public int getActualWidth() {
+ return mActualWidth > -1 ? mActualWidth : getWidth();
+ }
+
+ /**
+ * @param localX Click x from left of screen
+ * @param slop Margin of error within which we count x for valid click
+ * @param left Left of shelf, from left of screen
+ * @param right Right of shelf, from left of screen
+ * @return Whether click x was in view
+ */
+ @VisibleForTesting
+ public boolean isXInView(float localX, float slop, float left, float right) {
+ return (left - slop) <= localX && localX < (right + slop);
+ }
+
+ /**
+ * @param localY Click y from top of shelf
+ * @param slop Margin of error within which we count y for valid click
+ * @param top Top of shelf
+ * @param bottom Height of shelf
+ * @return Whether click y was in view
+ */
+ @VisibleForTesting
+ public boolean isYInView(float localY, float slop, float top, float bottom) {
+ return (top - slop) <= localY && localY < (bottom + slop);
+ }
+
+ /**
+ * @param localX Click x
+ * @param localY Click y
+ * @param slop Margin of error for valid click
+ * @return Whether this click was on the visible (non-clipped) part of the shelf
+ */
+ @Override
+ public boolean pointInView(float localX, float localY, float slop) {
+ final float containerWidth = getWidth();
+ final float shelfWidth = getActualWidth();
+
+ final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
+ final float right = isLayoutRtl() ? containerWidth : shelfWidth;
+
+ final float top = mClipTopAmount;
+ final float bottom = getActualHeight();
+
+ return isXInView(localX, slop, left, right)
+ && isYInView(localY, slop, top, bottom);
+ }
+
+ /**
* Update the shelf appearance based on the other notifications around it. This transforms
* the icons from the notification area into the shelf.
*/
@@ -732,11 +819,15 @@
// we always want to clip to our sides, such that nothing can draw outside of these bounds
int height = getResources().getDisplayMetrics().heightPixels;
mClipRect.set(0, -height, getWidth(), height);
- mShelfIcons.setClipBounds(mClipRect);
+ if (mShelfIcons != null) {
+ mShelfIcons.setClipBounds(mClipRect);
+ }
}
private void updateRelativeOffset() {
- mCollapsedIcons.getLocationOnScreen(mTmp);
+ if (mCollapsedIcons != null) {
+ mCollapsedIcons.getLocationOnScreen(mTmp);
+ }
getLocationOnScreen(mTmp);
}
@@ -831,9 +922,20 @@
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
- private class ShelfState extends ExpandableViewState {
+ public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
+ public int actualWidth = -1;
+
+ private void updateShelfWidth(View view) {
+ if (actualWidth < 0) {
+ return;
+ }
+ mActualWidth = actualWidth;
+ ActivatableNotificationView anv = (ActivatableNotificationView) view;
+ anv.getBackgroundNormal().setActualWidth(actualWidth);
+ mShelfIcons.setActualLayoutWidth(actualWidth);
+ }
@Override
public void applyToView(View view) {
@@ -846,19 +948,21 @@
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+ updateShelfWidth(view);
}
@Override
- public void animateTo(View child, AnimationProperties properties) {
+ public void animateTo(View view, AnimationProperties properties) {
if (!mShowNotificationShelf) {
return;
}
- super.animateTo(child, properties);
+ super.animateTo(view, properties);
setIndexOfFirstViewInShelf(firstViewInShelf);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+ updateShelfWidth(view);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 6c3a909..c74621d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -16,13 +16,19 @@
package com.android.systemui.statusbar;
-import android.content.Context;
-import android.os.AsyncTask;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -32,21 +38,75 @@
public class VibratorHelper {
private final Vibrator mVibrator;
- private final Context mContext;
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ private final Executor mExecutor;
/**
*/
@Inject
- public VibratorHelper(Context context) {
- mContext = context;
- mVibrator = context.getSystemService(Vibrator.class);
+ public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+ mExecutor = executor;
+ mVibrator = vibrator;
}
+ /**
+ * @see Vibrator#vibrate(long)
+ */
public void vibrate(final int effectId) {
- AsyncTask.execute(() ->
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() ->
mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */),
TOUCH_VIBRATION_ATTRIBUTES));
}
+
+ /**
+ * @see Vibrator#vibrate(int, String, VibrationEffect, String, VibrationAttributes)
+ */
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
+ String reason, @NonNull VibrationAttributes attributes) {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() -> mVibrator.vibrate(uid, opPkg, vibe, reason, attributes));
+ }
+
+ /**
+ * @see Vibrator#vibrate(VibrationEffect, AudioAttributes)
+ */
+ public void vibrate(@NonNull VibrationEffect effect, @NonNull AudioAttributes attributes) {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() -> mVibrator.vibrate(effect, attributes));
+ }
+
+ /**
+ * @see Vibrator#vibrate(VibrationEffect)
+ */
+ public void vibrate(@NotNull VibrationEffect effect) {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() -> mVibrator.vibrate(effect));
+ }
+
+ /**
+ * @see Vibrator#hasVibrator()
+ */
+ public boolean hasVibrator() {
+ return mVibrator != null && mVibrator.hasVibrator();
+ }
+
+ /**
+ * @see Vibrator#cancel()
+ */
+ public void cancel() {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(mVibrator::cancel);
+ }
}
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 0f21a6c..74c97fd 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
@@ -674,6 +674,12 @@
// having its summary promoted, regardless of how many children it has
Set<String> groupsWithChildrenLostToStability =
getGroupsWithChildrenLostToStability(shadeList);
+ // Like groups which lost a child to stability, any group which lost a child to promotion
+ // is exempt from having its summary promoted when it has no attached children.
+ Set<String> groupsWithChildrenLostToPromotionOrStability =
+ getGroupsWithChildrenLostToPromotion(shadeList);
+ groupsWithChildrenLostToPromotionOrStability.addAll(groupsWithChildrenLostToStability);
+
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
@@ -683,9 +689,9 @@
final boolean hasSummary = group.getSummary() != null;
if (hasSummary && children.size() == 0) {
- if (groupsWithChildrenLostToStability.contains(group.getKey())) {
- // This group lost a child on this run to stability, so it is exempt from
- // having its summary promoted to the top level, so prune it.
+ if (groupsWithChildrenLostToPromotionOrStability.contains(group.getKey())) {
+ // This group lost a child on this run to promotion or stability, so it is
+ // exempt from having its summary promoted to the top level, so prune it.
// It has no children, so it will just vanish.
pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
} else {
@@ -826,6 +832,26 @@
}
/**
+ * Collect the keys of any groups which have already lost a child to a {@link NotifPromoter}
+ * this run.
+ *
+ * These groups will be exempt from appearing without any children.
+ */
+ @NonNull
+ private Set<String> getGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList) {
+ ArraySet<String> groupsWithChildrenLostToPromotion = new ArraySet<>();
+ for (int i = 0; i < shadeList.size(); i++) {
+ final ListEntry tle = shadeList.get(i);
+ if (tle.getAttachState().getPromoter() != null) {
+ // This top-level-entry was part of a group, but was promoted out of it.
+ final String groupKey = tle.getRepresentativeEntry().getSbn().getGroupKey();
+ groupsWithChildrenLostToPromotion.add(groupKey);
+ }
+ }
+ return groupsWithChildrenLostToPromotion;
+ }
+
+ /**
* If a ListEntry was added to the shade list and then later removed (e.g. because it was a
* group that was broken up), this method will erase any bookkeeping traces of that addition
* and/or check that they were already erased.
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 0bf21af..0311324 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
@@ -15,9 +15,13 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Notification
+import android.app.Notification.GROUP_ALERT_SUMMARY
+import android.util.ArrayMap
import android.util.ArraySet
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
+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
@@ -29,13 +33,13 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
-import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
/**
@@ -54,27 +58,283 @@
*/
@CoordinatorScope
class HeadsUpCoordinator @Inject constructor(
+ private val mLogger: HeadsUpCoordinatorLogger,
+ private val mSystemClock: SystemClock,
private val mHeadsUpManager: HeadsUpManager,
private val mHeadsUpViewBinder: HeadsUpViewBinder,
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
private val mRemoteInputManager: NotificationRemoteInputManager,
@IncomingHeader private val mIncomingHeaderController: NodeController,
- @Main private val mExecutor: DelayableExecutor
+ @Main private val mExecutor: DelayableExecutor,
) : Coordinator {
+ private val mEntriesBindingUntil = ArrayMap<String, Long>()
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>()
override fun attach(pipeline: NotifPipeline) {
+ mNotifPipeline = pipeline
mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
pipeline.addCollectionListener(mNotifCollectionListener)
+ pipeline.addOnBeforeTransformGroupsListener(::onBeforeTransformGroups)
+ pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
pipeline.addPromoter(mNotifPromoter)
pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
}
private fun onHeadsUpViewBound(entry: NotificationEntry) {
mHeadsUpManager.showNotification(entry)
+ mEntriesBindingUntil.remove(entry.key)
+ }
+
+ /**
+ * Once the pipeline starts running, we can look through posted entries and quickly process
+ * any that don't have groups, and thus will never gave a group alert edge case.
+ */
+ fun onBeforeTransformGroups(list: List<ListEntry>) {
+ mNow = mSystemClock.currentTimeMillis()
+ if (mPostedEntries.isEmpty()) {
+ return
+ }
+ // Process all non-group adds/updates
+ mPostedEntries.values.toList().forEach { posted ->
+ if (!posted.entry.sbn.isGroup) {
+ handlePostedEntry(posted, "non-group")
+ mPostedEntries.remove(posted.key)
+ }
+ }
+ }
+
+ /**
+ * Once we have a nearly final shade list (not including what's pruned for inflation reasons),
+ * we know that stability and [NotifPromoter]s have been applied, so we can use the location of
+ * notifications in this list to determine what kind of group alert behavior should happen.
+ */
+ fun onBeforeFinalizeFilter(list: List<ListEntry>) {
+ // Nothing to do if there are no other adds/updates
+ if (mPostedEntries.isEmpty()) {
+ return
+ }
+ // Calculate a bunch of information about the logical group and the locations of group
+ // entries in the nearly-finalized shade list. These may be used in the per-group loop.
+ val postedEntriesByGroup = mPostedEntries.values.groupBy { it.entry.sbn.groupKey }
+ val logicalMembersByGroup = mNotifPipeline.allNotifs.asSequence()
+ .filter { postedEntriesByGroup.contains(it.sbn.groupKey) }
+ .groupBy { it.sbn.groupKey }
+ val groupLocationsByKey: Map<String, GroupLocation> by lazy { getGroupLocationsByKey(list) }
+ mLogger.logEvaluatingGroups(postedEntriesByGroup.size)
+ // For each group, determine which notification(s) for a group should alert.
+ postedEntriesByGroup.forEach { (groupKey, postedEntries) ->
+ // get and classify the logical members
+ val logicalMembers = logicalMembersByGroup[groupKey] ?: emptyList()
+ val logicalSummary = logicalMembers.find { it.sbn.notification.isGroupSummary }
+
+ // Report the start of this group's evaluation
+ mLogger.logEvaluatingGroup(groupKey, postedEntries.size, logicalMembers.size)
+
+ // If there is no logical summary, then there is no alert to transfer
+ if (logicalSummary == null) {
+ postedEntries.forEach { handlePostedEntry(it, "logical-summary-missing") }
+ return@forEach
+ }
+
+ // If summary isn't wanted to be heads up, then there is no alert to transfer
+ if (!isGoingToShowHunStrict(logicalSummary)) {
+ postedEntries.forEach { handlePostedEntry(it, "logical-summary-not-alerting") }
+ return@forEach
+ }
+
+ // The group is alerting! Overall goals:
+ // - Maybe transfer its alert to a child
+ // - Also let any/all newly alerting children still alert
+ var childToReceiveParentAlert: NotificationEntry?
+ var targetType = "undefined"
+
+ // If the parent is alerting, always look at the posted notification with the newest
+ // 'when', and if it is isolated with GROUP_ALERT_SUMMARY, then it should receive the
+ // parent's alert.
+ childToReceiveParentAlert =
+ findAlertOverride(postedEntries, groupLocationsByKey::getLocation)
+ if (childToReceiveParentAlert != null) {
+ targetType = "alertOverride"
+ }
+
+ // If the summary is Detached and we have not picked a receiver of the alert, then we
+ // need to look for the best child to alert in place of the summary.
+ val isSummaryAttached = groupLocationsByKey.contains(logicalSummary.key)
+ if (!isSummaryAttached && childToReceiveParentAlert == null) {
+ childToReceiveParentAlert =
+ findBestTransferChild(logicalMembers, groupLocationsByKey::getLocation)
+ if (childToReceiveParentAlert != null) {
+ targetType = "bestChild"
+ }
+ }
+
+ // If there is no child to receive the parent alert, then just handle the posted entries
+ // and return.
+ if (childToReceiveParentAlert == null) {
+ postedEntries.forEach { handlePostedEntry(it, "no-transfer-target") }
+ return@forEach
+ }
+
+ // At this point we just need to initiate the transfer
+ val summaryUpdate = mPostedEntries[logicalSummary.key]
+
+ // If the summary was not attached, then remove the alert from the detached summary.
+ // Otherwise we can simply ignore its posted update.
+ if (!isSummaryAttached) {
+ val summaryUpdateForRemoval = summaryUpdate?.also {
+ it.shouldHeadsUpEver = false
+ } ?: PostedEntry(logicalSummary,
+ wasAdded = false,
+ wasUpdated = false,
+ shouldHeadsUpEver = false,
+ shouldHeadsUpAgain = false,
+ isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+ isBinding = isEntryBinding(logicalSummary),
+ )
+ // If we transfer the alert and the summary isn't even attached, that means we
+ // should ensure the summary is no longer alerting, so we remove it here.
+ handlePostedEntry(summaryUpdateForRemoval, "detached-summary-remove-alert")
+ } else if (summaryUpdate!=null) {
+ mLogger.logPostedEntryWillNotEvaluate(summaryUpdate, "attached-summary-transferred")
+ }
+
+ // Handle all posted entries -- if the child receiving the parent's alert is in the
+ // list, then set its flags to ensure it alerts.
+ var didAlertChildToReceiveParentAlert = false
+ postedEntries.asSequence()
+ .filter { it.key != logicalSummary.key }
+ .forEach { postedEntry ->
+ if (childToReceiveParentAlert.key == postedEntry.key) {
+ // Update the child's posted update so that it
+ postedEntry.shouldHeadsUpEver = true
+ postedEntry.shouldHeadsUpAgain = true
+ handlePostedEntry(postedEntry, "child-alert-transfer-target-$targetType")
+ didAlertChildToReceiveParentAlert = true
+ } else {
+ handlePostedEntry(postedEntry, "child-alert-non-target")
+ }
+ }
+
+ // If the child receiving the alert was not updated on this tick (which can happen in a
+ // standard alert transfer scenario), then construct an update so that we can apply it.
+ if (!didAlertChildToReceiveParentAlert) {
+ val posted = PostedEntry(
+ childToReceiveParentAlert,
+ wasAdded = false,
+ wasUpdated = false,
+ shouldHeadsUpEver = true,
+ shouldHeadsUpAgain = true,
+ isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+ isBinding = isEntryBinding(childToReceiveParentAlert),
+ )
+ handlePostedEntry(posted, "non-posted-child-alert-transfer-target-$targetType")
+ }
+ }
+ // After this method runs, all posted entries should have been handled (or skipped).
+ mPostedEntries.clear()
+ }
+
+ /**
+ * Find the posted child with the newest when, and return it if it is isolated and has
+ * GROUP_ALERT_SUMMARY so that it can be alerted.
+ */
+ private fun findAlertOverride(
+ postedEntries: List<PostedEntry>,
+ locationLookupByKey: (String) -> GroupLocation,
+ ): NotificationEntry? = postedEntries.asSequence()
+ .filter { posted -> !posted.entry.sbn.notification.isGroupSummary }
+ .sortedBy { posted -> -posted.entry.sbn.notification.`when` }
+ .firstOrNull()
+ ?.let { posted ->
+ posted.entry.takeIf { entry ->
+ locationLookupByKey(entry.key) == GroupLocation.Isolated
+ && entry.sbn.notification.groupAlertBehavior == GROUP_ALERT_SUMMARY
+ }
+ }
+
+ /**
+ * Of children which are attached, look for the child to receive the notification:
+ * First prefer children which were updated, then looking for the ones with the newest 'when'
+ */
+ private fun findBestTransferChild(
+ logicalMembers: List<NotificationEntry>,
+ locationLookupByKey: (String) -> GroupLocation,
+ ): NotificationEntry? = logicalMembers.asSequence()
+ .filter { !it.sbn.notification.isGroupSummary }
+ .filter { locationLookupByKey(it.key) != GroupLocation.Detached }
+ .sortedWith(compareBy(
+ { !mPostedEntries.contains(it.key) },
+ { -it.sbn.notification.`when` },
+ ))
+ .firstOrNull()
+
+ private fun getGroupLocationsByKey(list: List<ListEntry>): Map<String, GroupLocation> =
+ mutableMapOf<String, GroupLocation>().also { map ->
+ list.forEach { topLevelEntry ->
+ when (topLevelEntry) {
+ is NotificationEntry -> map[topLevelEntry.key] = GroupLocation.Isolated
+ is GroupEntry -> {
+ topLevelEntry.summary?.let { summary ->
+ map[summary.key] = GroupLocation.Summary
+ }
+ topLevelEntry.children.forEach { child ->
+ map[child.key] = GroupLocation.Child
+ }
+ }
+ else -> error("unhandled type $topLevelEntry")
+ }
+ }
+ }
+
+ private val mPostedEntries = LinkedHashMap<String, PostedEntry>()
+
+ fun handlePostedEntry(posted: PostedEntry, scenario: String) {
+ mLogger.logPostedEntryWillEvaluate(posted, scenario)
+ if (posted.wasAdded) {
+ if (posted.shouldHeadsUpEver) {
+ bindForAsyncHeadsUp(posted)
+ }
+ } else {
+ if (posted.isHeadsUpAlready) {
+ // NOTE: This might be because we're alerting (i.e. tracked by HeadsUpManager) OR
+ // it could be because we're binding, and that will affect the next step.
+ if (posted.shouldHeadsUpEver) {
+ // If alerting, we need to post an update. Otherwise we're still binding,
+ // and we can just let that finish.
+ if (posted.isAlerting) {
+ mHeadsUpManager.updateNotification(posted.key, posted.shouldHeadsUpAgain)
+ }
+ } else {
+ if (posted.isAlerting) {
+ // We don't want this to be interrupting anymore, let's remove it
+ mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/)
+ } else {
+ // Don't let the bind finish
+ cancelHeadsUpBind(posted.entry)
+ }
+ }
+ } else if (posted.shouldHeadsUpEver && posted.shouldHeadsUpAgain) {
+ // This notification was updated to be heads up, show it!
+ bindForAsyncHeadsUp(posted)
+ }
+ }
+ }
+
+ private fun cancelHeadsUpBind(entry: NotificationEntry) {
+ mEntriesBindingUntil.remove(entry.key)
+ mHeadsUpViewBinder.abortBindCallback(entry)
+ }
+
+ private fun bindForAsyncHeadsUp(posted: PostedEntry) {
+ // TODO: Add a guarantee to bindHeadsUpView of some kind of callback if the bind is
+ // cancelled so that we don't need to have this sad timeout hack.
+ mEntriesBindingUntil[posted.key] = mNow + BIND_TIMEOUT
+ mHeadsUpViewBinder.bindHeadsUpView(posted.entry, this::onHeadsUpViewBound)
}
private val mNotifCollectionListener = object : NotifCollectionListener {
@@ -82,9 +342,17 @@
* Notification was just added and if it should heads up, bind the view and then show it.
*/
override fun onEntryAdded(entry: NotificationEntry) {
- if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
- mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
- }
+ // shouldHeadsUp includes check for whether this notification should be filtered
+ val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+ mPostedEntries[entry.key] = PostedEntry(
+ entry,
+ wasAdded = true,
+ wasUpdated = false,
+ shouldHeadsUpEver = shouldHeadsUpEver,
+ shouldHeadsUpAgain = true,
+ isAlerting = false,
+ isBinding = false,
+ )
}
/**
@@ -93,22 +361,26 @@
* up again.
*/
override fun onEntryUpdated(entry: NotificationEntry) {
- val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification)
- // includes check for whether this notification should be filtered:
- val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
- val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key)
- if (wasHeadsUp) {
- if (shouldHeadsUp) {
- mHeadsUpManager.updateNotification(entry.key, hunAgain)
- } else {
- // We don't want this to be interrupting anymore, let's remove it
- mHeadsUpManager.removeNotification(
- entry.key, false /* removeImmediately */
- )
- }
- } else if (shouldHeadsUp && hunAgain) {
- // This notification was updated to be heads up, show it!
- mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
+ val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+ val shouldHeadsUpAgain = shouldHunAgain(entry)
+ val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+ val isBinding = isEntryBinding(entry)
+ mPostedEntries.compute(entry.key) { _, value ->
+ value?.also { update ->
+ update.wasUpdated = true
+ update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver
+ update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain
+ update.isAlerting = isAlerting
+ update.isBinding = isBinding
+ } ?: PostedEntry(
+ entry,
+ wasAdded = false,
+ wasUpdated = true,
+ shouldHeadsUpEver = shouldHeadsUpEver,
+ shouldHeadsUpAgain = shouldHeadsUpAgain,
+ isAlerting = isAlerting,
+ isBinding = isBinding,
+ )
}
}
@@ -116,8 +388,12 @@
* Stop alerting HUNs that are removed from the notification collection
*/
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ mPostedEntries.remove(entry.key)
+ cancelHeadsUpBind(entry)
val entryKey = entry.key
if (mHeadsUpManager.isAlerting(entryKey)) {
+ // TODO: This should probably know the RemoteInputCoordinator's conditions,
+ // or otherwise reference that coordinator's state, rather than replicate its logic
val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
!NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY)
mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput)
@@ -129,6 +405,14 @@
}
}
+ /**
+ * Checks whether an update for a notification warrants an alert for the user.
+ */
+ private fun shouldHunAgain(entry: NotificationEntry): Boolean {
+ return (!entry.hasInterrupted() ||
+ (entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
+ }
+
private val mLifetimeExtender = object : NotifLifetimeExtender {
override fun getName() = TAG
@@ -164,11 +448,13 @@
private val mNotifPromoter = object : NotifPromoter(TAG) {
override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean =
- isCurrentlyShowingHun(entry)
+ isGoingToShowHunNoRetract(entry)
}
val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) {
- override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry)
+ override fun isInSection(entry: ListEntry): Boolean =
+ // TODO: This check won't notice if a child of the group is going to HUN...
+ isGoingToShowHunNoRetract(entry)
override fun getHeaderNodeController(): NodeController? =
// TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
@@ -186,7 +472,34 @@
private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key)
- private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key)
+ private fun isEntryBinding(entry: ListEntry): Boolean {
+ val bindingUntil = mEntriesBindingUntil[entry.key]
+ return bindingUntil != null && bindingUntil >= mNow
+ }
+
+ /**
+ * Whether the notification is already alerting or binding so that it can imminently alert
+ */
+ private fun isAttemptingToShowHun(entry: ListEntry) =
+ mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+
+ /**
+ * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
+ * has been updated so that it should alert this update. This method is permissive because it
+ * returns `true` even if the update would (in isolation of its group) cause the alert to be
+ * retracted. This is important for not retracting transferred group alerts.
+ */
+ private fun isGoingToShowHunNoRetract(entry: ListEntry) =
+ mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry)
+
+ /**
+ * If the notification has been updated, then whether it should HUN in isolation, otherwise
+ * defers to the already alerting/binding state of [isAttemptingToShowHun]. This method is
+ * strict because any update which would revoke the alert supersedes the current
+ * alerting/binding state.
+ */
+ private fun isGoingToShowHunStrict(entry: ListEntry) =
+ mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry)
private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
if (mNotifsExtendingLifetime.remove(entry)) {
@@ -196,5 +509,29 @@
companion object {
private const val TAG = "HeadsUpCoordinator"
+ private const val BIND_TIMEOUT = 1000L
}
-}
\ No newline at end of file
+
+ data class PostedEntry(
+ val entry: NotificationEntry,
+ val wasAdded: Boolean,
+ var wasUpdated: Boolean,
+ var shouldHeadsUpEver: Boolean,
+ var shouldHeadsUpAgain: Boolean,
+ var isAlerting: Boolean,
+ var isBinding: Boolean,
+ ) {
+ val key = entry.key
+ val isHeadsUpAlready: Boolean
+ get() = isAlerting || isBinding
+ val calculateShouldBeHeadsUpStrict: Boolean
+ get() = shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain || isHeadsUpAlready)
+ val calculateShouldBeHeadsUpNoRetract: Boolean
+ get() = isHeadsUpAlready || (shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain))
+ }
+}
+
+private enum class GroupLocation { Detached, Isolated, Summary, Child }
+
+private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation =
+ getOrDefault(key, GroupLocation.Detached)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
new file mode 100644
index 0000000..204a494
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.Log
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+private const val TAG = "HeadsUpCoordinator"
+
+class HeadsUpCoordinatorLogger constructor(
+ private val buffer: LogBuffer,
+ private val verbose: Boolean,
+) {
+ @Inject
+ constructor(@NotificationHeadsUpLog buffer: LogBuffer) :
+ this(buffer, Log.isLoggable(TAG, Log.VERBOSE))
+
+ fun logPostedEntryWillEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ str1 = posted.key
+ str2 = reason
+ bool1 = posted.shouldHeadsUpEver
+ bool2 = posted.shouldHeadsUpAgain
+ }, {
+ "will evaluate posted entry $str1:" +
+ " reason=$str2 shouldHeadsUpEver=$bool1 shouldHeadsUpAgain=$bool2"
+ })
+ }
+
+ fun logPostedEntryWillNotEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ str1 = posted.key
+ str2 = reason
+ }, {
+ "will not evaluate posted entry $str1: reason=$str2"
+ })
+ }
+
+ fun logEvaluatingGroups(numGroups: Int) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ int1 = numGroups
+ }, {
+ "evaluating groups for alert transfer: $int1"
+ })
+ }
+
+ fun logEvaluatingGroup(groupKey: String, numPostedEntries: Int, logicalGroupSize: Int) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ str1 = groupKey
+ int1 = numPostedEntries
+ int2 = logicalGroupSize
+ }, {
+ "evaluating group for alert transfer: $str1" +
+ " numPostedEntries=$int1 logicalGroupSize=$int2"
+ })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 5d6d0f7..fca2aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -162,6 +162,13 @@
updateBackgroundTint();
}
+ /**
+ * @return The background of this view.
+ */
+ public NotificationBackgroundView getBackgroundNormal() {
+ return mBackgroundNormal;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 0f615aa..c640ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -29,6 +29,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
/**
@@ -39,15 +40,17 @@
private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
- private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
private final float[] mCornerRadii = new float[8];
private boolean mBottomIsRounded;
private int mBackgroundTop;
private boolean mBottomAmountClips = true;
+ private int mActualHeight = -1;
+ private int mActualWidth = -1;
private boolean mExpandAnimationRunning;
- private float mActualWidth;
+ private int mExpandAnimationWidth = -1;
+ private int mExpandAnimationHeight = -1;
private int mDrawableAlpha = 255;
private boolean mIsPressedAllowed;
@@ -59,11 +62,12 @@
@Override
protected void onDraw(Canvas canvas) {
- if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+ if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
|| mExpandAnimationRunning) {
canvas.save();
if (!mExpandAnimationRunning) {
- canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ canvas.clipRect(0, mClipTopAmount, getWidth(),
+ getActualHeight() - mClipBottomAmount);
}
draw(canvas, mBackground);
canvas.restore();
@@ -73,17 +77,23 @@
private void draw(Canvas canvas, Drawable drawable) {
if (drawable != null) {
int top = mBackgroundTop;
- int bottom = mActualHeight;
+ int bottom = getActualHeight();
if (mBottomIsRounded
&& mBottomAmountClips
&& !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
- int left = 0;
- int right = getWidth();
+ final boolean isRtl = isLayoutRtl();
+ final int width = getWidth();
+ final int actualWidth = getActualWidth();
+
+ int left = isRtl ? width - actualWidth : 0;
+ int right = isRtl ? width : actualWidth;
+
if (mExpandAnimationRunning) {
- left = (int) ((getWidth() - mActualWidth) / 2.0f);
- right = (int) (left + mActualWidth);
+ // Horizontally center this background view inside of the container
+ left = (int) ((width - actualWidth) / 2.0f);
+ right = (int) (left + actualWidth);
}
drawable.setBounds(left, top, right, bottom);
drawable.draw(canvas);
@@ -152,8 +162,26 @@
invalidate();
}
- public int getActualHeight() {
- return mActualHeight;
+ private int getActualHeight() {
+ if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
+ return mExpandAnimationHeight;
+ } else if (mActualHeight > -1) {
+ return mActualHeight;
+ }
+ return getHeight();
+ }
+
+ public void setActualWidth(int actualWidth) {
+ mActualWidth = actualWidth;
+ }
+
+ private int getActualWidth() {
+ if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
+ return mExpandAnimationWidth;
+ } else if (mActualWidth > -1) {
+ return mActualWidth;
+ }
+ return getWidth();
}
public void setClipTopAmount(int clipTopAmount) {
@@ -241,9 +269,9 @@
}
/** Set the current expand animation size. */
- public void setExpandAnimationSize(int actualWidth, int actualHeight) {
- mActualHeight = actualHeight;
- mActualWidth = actualWidth;
+ public void setExpandAnimationSize(int width, int height) {
+ mExpandAnimationHeight = width;
+ mExpandAnimationWidth = height;
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 87de4ca..9f6a81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -202,6 +202,7 @@
private int mBottomMargin;
private int mBottomInset = 0;
private float mQsExpansionFraction;
+ private final int mSplitShadeMinContentHeight;
/**
* The algorithm which calculates the properties for our children
@@ -583,6 +584,8 @@
.getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
+ mSplitShadeMinContentHeight = res.getDimensionPixelSize(
+ R.dimen.nssl_split_shade_min_content_height);
mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
minHeight, maxHeight);
mExpandHelper.setEventSource(this);
@@ -3925,7 +3928,17 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
int getEmptyBottomMargin() {
- return Math.max(mMaxLayoutHeight - mContentHeight, 0);
+ int contentHeight;
+ if (mShouldUseSplitNotificationShade) {
+ // When in split shade and there are no notifications, the height can be too low, as
+ // it is based on notifications bottom, which is lower on split shade.
+ // Here we prefer to use at least a minimum height defined for split shade.
+ // Otherwise the expansion motion is too fast.
+ contentHeight = Math.max(mSplitShadeMinContentHeight, mContentHeight);
+ } else {
+ contentHeight = mContentHeight;
+ }
+ return Math.max(mMaxLayoutHeight - contentHeight, 0);
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -5484,6 +5497,17 @@
}
/**
+ * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states.
+ * Once the lockscreen to shade transition completes and the shade is 100% open
+ * LockscreenShadeTransitionController resets fraction to 0
+ * where it remains until the next lockscreen-to-shade transition.
+ */
+ public void setFractionToShade(float fraction) {
+ mShelf.setFractionToShade(fraction);
+ requestChildrenUpdate();
+ }
+
+ /**
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
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 0d0e5e8..334128a 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
@@ -1515,10 +1515,18 @@
}
/**
- * Set the amount of pixels we have currently dragged down if we're transitioning to the full
- * shade. 0.0f means we're not transitioning yet.
+ * @param amount The amount of pixels we have currently dragged down
+ * for the lockscreen to shade transition. 0f for all other states.
+ * @param fraction The fraction of lockscreen to shade transition.
+ * 0f for all other states.
+ *
+ * Once the lockscreen to shade transition completes and the shade is 100% open,
+ * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
+ * until the next lockscreen-to-shade transition.
*/
- public void setTransitionToFullShadeAmount(float amount) {
+ public void setTransitionToFullShadeAmount(float amount, float fraction) {
+ mView.setFractionToShade(fraction);
+
float extraTopInset = 0.0f;
if (mStatusBarStateController.getState() == KEYGUARD) {
float overallProgress = MathUtils.saturate(amount / mView.getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c09c485..febf2b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -135,7 +135,8 @@
}
}.setDuration(CONTENT_FADE_DURATION);
- private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5;
+ private static final int MAX_ICONS_ON_AOD = 5;
+ public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
public static final int MAX_STATIC_ICONS = 4;
private static final int MAX_DOTS = 1;
@@ -386,6 +387,19 @@
}
/**
+ * @return Width of shelf for the given number of icons and overflow dot
+ */
+ public int calculateWidthFor(int numMaxIcons) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ return (int) (getActualPaddingStart()
+ + numMaxIcons * mIconSize
+ + mOverflowWidth
+ + getActualPaddingEnd());
+ }
+
+ /**
* Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
* If this is not a whole number, the fraction means by how much the icon is appearing.
@@ -394,7 +408,7 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK :
+ int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = getMaxOverflowStart();
@@ -414,7 +428,7 @@
}
boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
&& iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- boolean noOverflowAfter = i == childCount - 1;
+ boolean isLastChild = i == childCount - 1;
float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
? ((StatusBarIconView) view).getIconScaleIncreased()
: 1f;
@@ -423,10 +437,10 @@
: StatusBarIconView.STATE_ICON;
boolean isOverflowing =
- (translationX > (noOverflowAfter ? layoutEnd - mIconSize
+ (translationX > (isLastChild ? layoutEnd - mIconSize
: overflowStart - mIconSize));
if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
- firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
+ firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
mVisualOverflowStart = layoutEnd - mOverflowWidth;
if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
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 cfa2456..9d3f19a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2484,7 +2484,6 @@
mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
mSplitShadeHeaderController.setShadeExpanded(mQsVisible);
- mKeyguardStatusBarViewController.updateViewState();
if (mCommunalViewController != null) {
mCommunalViewController.updateQsExpansion(qsExpansionFraction);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index e59d2f23..d0fb91c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -18,6 +18,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.statusbar.tv.VpnStatusObserver;
import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import dagger.Binds;
@@ -34,4 +35,9 @@
@IntoMap
@ClassKey(TvNotificationHandler.class)
CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui);
+
+ @Binds
+ @IntoMap
+ @ClassKey(VpnStatusObserver.class)
+ CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 11725ef..57c7f11 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -33,7 +33,6 @@
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IVolumeController;
-import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import android.media.VolumePolicy;
@@ -47,7 +46,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
@@ -68,6 +66,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.ThreadFactory;
@@ -78,7 +77,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
@@ -135,7 +133,7 @@
protected C mCallbacks = new C();
private final State mState = new State();
protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
- private final Optional<Vibrator> mVibrator;
+ private final VibratorHelper mVibrator;
private final boolean mHasVibrator;
private boolean mShowA11yStream;
private boolean mShowVolumeDialog;
@@ -173,7 +171,7 @@
ThreadFactory theadFactory,
AudioManager audioManager,
NotificationManager notificationManager,
- Optional<Vibrator> optionalVibrator,
+ VibratorHelper vibrator,
IAudioService iAudioService,
AccessibilityManager accessibilityManager,
PackageManager packageManager,
@@ -199,8 +197,8 @@
mBroadcastDispatcher = broadcastDispatcher;
mObserver.init();
mReceiver.init();
- mVibrator = optionalVibrator;
- mHasVibrator = mVibrator.isPresent() && mVibrator.get().hasVibrator();
+ mVibrator = vibrator;
+ mHasVibrator = mVibrator.hasVibrator();
mAudioService = iAudioService;
boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -393,8 +391,7 @@
}
public void vibrate(VibrationEffect effect) {
- mVibrator.ifPresent(
- vibrator -> vibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES));
+ mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES);
}
public boolean hasVibrator() {
@@ -402,7 +399,7 @@
}
private void onNotifyVisibleW(boolean visible) {
- if (mDestroyed) return;
+ if (mDestroyed) return;
mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
if (!visible) {
if (updateActiveStreamW(-1)) {
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 159bdba..35e838b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -44,7 +44,6 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.VibrationAttributes;
-import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -64,6 +63,7 @@
import com.android.systemui.plugins.FalsingManager;
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.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -141,7 +141,7 @@
@Mock
private ScreenLifecycle mScreenLifecycle;
@Mock
- private Vibrator mVibrator;
+ private VibratorHelper mVibrator;
@Mock
private UdfpsHapticsSimulator mUdfpsHapticsSimulator;
@Mock
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 55509d1..9908d44 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
@@ -20,13 +20,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.TaskViewFactory
-import dagger.Lazy
-import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,15 +39,14 @@
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.Optional
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlActionCoordinatorImplTest : SysuiTestCase() {
@Mock
- private lateinit var uiController: ControlsUiController
- @Mock
- private lateinit var lazyUiController: Lazy<ControlsUiController>
+ private lateinit var vibratorHelper: VibratorHelper
@Mock
private lateinit var keyguardStateController: KeyguardStateController
@Mock
@@ -59,8 +56,6 @@
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
- private lateinit var globalActionsComponent: GlobalActionsComponent
- @Mock
private lateinit var taskViewFactory: Optional<TaskViewFactory>
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var cvh: ControlViewHolder
@@ -86,11 +81,9 @@
uiExecutor,
activityStarter,
keyguardStateController,
- globalActionsComponent,
taskViewFactory,
- getFakeBroadcastDispatcher(),
- lazyUiController,
- metricsLogger
+ metricsLogger,
+ vibratorHelper
))
`when`(cvh.cws.ci.controlId).thenReturn(ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
new file mode 100644
index 0000000..2cb1939
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.dump
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
+
+/**
+ * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
+ */
+fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
+ LogBuffer(name, 50, 50, LogcatEchoTrackerAlways())
+
+/**
+ * A [LogcatEchoTracker] that always allows echoing to the logcat.
+ */
+class LogcatEchoTrackerAlways : LogcatEchoTracker {
+ override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = true
+ override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e3a7e3b..71fc8ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -115,6 +116,7 @@
@Mock private PackageManager mPackageManager;
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
+ @Mock private VibratorHelper mVibratorHelper;
@Mock private StatusBar mStatusBar;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
@@ -143,7 +145,7 @@
mTelephonyListenerManager,
mGlobalSettings,
mSecureSettings,
- null,
+ mVibratorHelper,
mResources,
mConfigurationController,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index d7c00fb..5ed1d65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -38,7 +38,6 @@
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
@@ -62,6 +61,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,7 +104,7 @@
private @Mock DumpManager mDumpManager;
private @Mock AccessibilityManager mAccessibilityManager;
private @Mock ConfigurationController mConfigurationController;
- private @Mock Vibrator mVibrator;
+ private @Mock VibratorHelper mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
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 42647f7..d51d370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -227,7 +227,7 @@
fun testDragDownAmountDoesntCallOutInLockedDownShade() {
whenever(nsslController.isInLockedDownShade).thenReturn(true)
transitionController.dragDownAmount = 10f
- verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -238,7 +238,7 @@
@Test
fun testDragDownAmountCallsOut() {
transitionController.dragDownAmount = 10f
- verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+ verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
new file mode 100644
index 0000000..ad908e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.statusbar
+
+import android.media.AudioAttributes
+import android.os.UserHandle
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+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.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.Executor
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class VibratorHelperTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var vibrator: Vibrator
+ @Mock lateinit var executor: Executor
+ @Captor lateinit var backgroundTaskCaptor: ArgumentCaptor<Runnable>
+ lateinit var vibratorHelper: VibratorHelper
+
+ @Before
+ fun setup() {
+ vibratorHelper = VibratorHelper(vibrator, executor)
+ whenever(vibrator.hasVibrator()).thenReturn(true)
+ }
+
+ @Test
+ fun testVibrate() {
+ vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
+ verifyAsync().vibrate(any(VibrationEffect::class.java),
+ any(VibrationAttributes::class.java))
+ }
+
+ @Test
+ fun testVibrate2() {
+ vibratorHelper.vibrate(UserHandle.USER_CURRENT, "package",
+ mock(VibrationEffect::class.java), "reason",
+ mock(VibrationAttributes::class.java))
+ verifyAsync().vibrate(eq(UserHandle.USER_CURRENT), eq("package"),
+ any(VibrationEffect::class.java), eq("reason"),
+ any(VibrationAttributes::class.java))
+ }
+
+ @Test
+ fun testVibrate3() {
+ vibratorHelper.vibrate(mock(VibrationEffect::class.java), mock(AudioAttributes::class.java))
+ verifyAsync().vibrate(any(VibrationEffect::class.java), any(AudioAttributes::class.java))
+ }
+
+ @Test
+ fun testVibrate4() {
+ vibratorHelper.vibrate(mock(VibrationEffect::class.java))
+ verifyAsync().vibrate(any(VibrationEffect::class.java))
+ }
+
+ @Test
+ fun testHasVibrator() {
+ assertThat(vibratorHelper.hasVibrator()).isTrue()
+ verify(vibrator).hasVibrator()
+ }
+
+ @Test
+ fun testCancel() {
+ vibratorHelper.cancel()
+ verifyAsync().cancel()
+ }
+
+ private fun verifyAsync(): Vibrator {
+ verify(executor).execute(backgroundTaskCaptor.capture())
+ verify(vibrator).hasVibrator()
+ backgroundTaskCaptor.value.run()
+
+ return verify(vibrator)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 2e84482..8fb066b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -368,6 +368,50 @@
}
@Test
+ public void testGroupsWhoLoseAllChildrenToPromotionSuppressSummary() {
+ // GIVEN a group with two children
+ addGroupChild(0, PACKAGE_2, GROUP_1);
+ addGroupSummary(1, PACKAGE_2, GROUP_1);
+ addGroupChild(2, PACKAGE_2, GROUP_1);
+
+ // GIVEN a promoter that will promote one of children to top level
+ mListBuilder.addPromoter(new IdPromoter(0, 2));
+
+ // WHEN we build the list
+ dispatchBuild();
+
+ // THEN both children end up at top level (because group is now too small)
+ verifyBuiltList(
+ notif(0),
+ notif(2)
+ );
+
+ // THEN the summary is discarded
+ assertNull(mEntrySet.get(1).getParent());
+ }
+
+ @Test
+ public void testGroupsWhoLoseOnlyChildToPromotionSuppressSummary() {
+ // GIVEN a group with two children
+ addGroupChild(0, PACKAGE_2, GROUP_1);
+ addGroupSummary(1, PACKAGE_2, GROUP_1);
+
+ // GIVEN a promoter that will promote one of children to top level
+ mListBuilder.addPromoter(new IdPromoter(0));
+
+ // WHEN we build the list
+ dispatchBuild();
+
+ // THEN both children end up at top level (because group is now too small)
+ verifyBuiltList(
+ notif(0)
+ );
+
+ // THEN the summary is discarded
+ assertNull(mEntrySet.get(1).getParent());
+ }
+
+ @Test
public void testPreviousParentsAreSetProperly() {
// GIVEN a notification that is initially added to the list
PackageFilter filter = new PackageFilter(PACKAGE_2);
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 c67a233..144eefb 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
@@ -15,14 +15,20 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Notification.GROUP_ALERT_ALL
+import android.app.Notification.GROUP_ALERT_SUMMARY
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.statusbar.NotificationRemoteInputManager
+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.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -32,6 +38,7 @@
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
+import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.FakeExecutor
@@ -64,10 +71,13 @@
private lateinit var mCollectionListener: NotifCollectionListener
private lateinit var mNotifPromoter: NotifPromoter
private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender
+ private lateinit var mBeforeTransformGroupsListener: OnBeforeTransformGroupsListener
+ private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener
private lateinit var mNotifSectioner: NotifSectioner
private val mNotifPipeline: NotifPipeline = mock()
+ private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
private val mHeadsUpManager: HeadsUpManager = mock()
private val mHeadsUpViewBinder: HeadsUpViewBinder = mock()
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
@@ -76,12 +86,24 @@
private val mHeaderController: NodeController = mock()
private lateinit var mEntry: NotificationEntry
- private val mExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var mGroupSummary: NotificationEntry
+ private lateinit var mGroupPriority: NotificationEntry
+ private lateinit var mGroupSibling1: NotificationEntry
+ private lateinit var mGroupSibling2: NotificationEntry
+ private lateinit var mGroupChild1: NotificationEntry
+ private lateinit var mGroupChild2: NotificationEntry
+ private lateinit var mGroupChild3: NotificationEntry
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
private val mHuns: ArrayList<NotificationEntry> = ArrayList()
+ private lateinit var mHelper: NotificationGroupTestHelper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ mHelper = NotificationGroupTestHelper(mContext)
mCoordinator = HeadsUpCoordinator(
+ mLogger,
+ mSystemClock,
mHeadsUpManager,
mHeadsUpViewBinder,
mNotificationInterruptStateProvider,
@@ -100,6 +122,12 @@
mNotifLifetimeExtender = withArgCaptor {
verify(mNotifPipeline).addNotificationLifetimeExtender(capture())
}
+ mBeforeTransformGroupsListener = withArgCaptor {
+ verify(mNotifPipeline).addOnBeforeTransformGroupsListener(capture())
+ }
+ mBeforeFinalizeFilterListener = withArgCaptor {
+ verify(mNotifPipeline).addOnBeforeFinalizeFilterListener(capture())
+ }
mOnHeadsUpChangedListener = withArgCaptor {
verify(mHeadsUpManager).addListener(capture())
}
@@ -116,6 +144,16 @@
mNotifSectioner = mCoordinator.sectioner
mNotifLifetimeExtender.setCallback(mEndLifetimeExtension)
mEntry = NotificationEntryBuilder().build()
+ // Same summary we can use for either set of children
+ mGroupSummary = mHelper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500)
+ // One set of children with GROUP_ALERT_SUMMARY
+ mGroupPriority = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400)
+ mGroupSibling1 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300)
+ mGroupSibling2 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200)
+ // Another set of children with GROUP_ALERT_ALL
+ mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
+ mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
+ mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
}
@Test
@@ -156,6 +194,34 @@
}
@Test
+ fun testPromotesAddedHUN() {
+ // GIVEN the current entry should heads up
+ whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+ // WHEN the notification is added but not yet binding
+ mCollectionListener.onEntryAdded(mEntry)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
+
+ // THEN only promote mEntry
+ assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+ }
+
+ @Test
+ fun testPromotesBindingHUN() {
+ // GIVEN the current entry should heads up
+ whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+ // WHEN the notification started binding on the previous run
+ mCollectionListener.onEntryAdded(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), any())
+
+ // THEN only promote mEntry
+ assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+ }
+
+ @Test
fun testPromotesCurrentHUN() {
// GIVEN the current HUN is set to mEntry
addHUN(mEntry)
@@ -198,6 +264,9 @@
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
mCollectionListener.onEntryAdded(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ verify(mHeadsUpManager, never()).showNotification(mEntry)
withArgCaptor<BindCallback> {
verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture())
}.onBindFinished(mEntry)
@@ -211,6 +280,8 @@
// WHEN a notification shouldn't HUN and its inflation is finished
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false)
mCollectionListener.onEntryAdded(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
// THEN we never bind the heads up view or tell HeadsUpManager to show the notification
verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
@@ -235,4 +306,296 @@
whenever(mHeadsUpManager.topEntry).thenReturn(entry)
mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
}
-}
\ No newline at end of file
+
+ @Test
+ fun testTransferIsolatedChildAlert_withGroupAlertSummary() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupSibling1))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupSibling1))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupSibling1))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupSibling1)
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupSibling1)
+ }
+
+ @Test
+ fun testTransferIsolatedChildAlert_withGroupAlertAll() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupChild1))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupChild1))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupChild1))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupChild1)
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupChild1)
+ }
+
+ @Test
+ fun testTransferTwoIsolatedChildAlert_withGroupAlertSummary() {
+ // WHEN a notification should HUN and its inflation is finished
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mCollectionListener.onEntryAdded(mGroupSibling2)
+ val entryList = listOf(mGroupSibling1, mGroupSibling2)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupSibling1)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ // THEN we tell the HeadsUpManager to show the notification
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testTransferTwoIsolatedChildAlert_withGroupAlertAll() {
+ // WHEN a notification should HUN and its inflation is finished
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2, mGroupPriority))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mCollectionListener.onEntryAdded(mGroupChild2)
+ val entryList = listOf(mGroupChild1, mGroupChild2)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupChild1)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+ // THEN we tell the HeadsUpManager to show the notification
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupChild1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ }
+
+ @Test
+ fun testTransferToPriorityOnAddWithTwoSiblings() {
+ // WHEN a notification should HUN and its inflation is finished
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupPriority)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mCollectionListener.onEntryAdded(mGroupSibling2)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupPriority)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ // THEN we tell the HeadsUpManager to show the notification
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testTransferToPriorityOnUpdateWithTwoSiblings() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryUpdated(mGroupSummary)
+ mCollectionListener.onEntryUpdated(mGroupPriority)
+ mCollectionListener.onEntryUpdated(mGroupSibling1)
+ mCollectionListener.onEntryUpdated(mGroupSibling2)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupPriority)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testTransferToPriorityOnUpdateWithTwoNonUpdatedSiblings() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryUpdated(mGroupSummary)
+ mCollectionListener.onEntryUpdated(mGroupPriority)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupPriority)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testNoTransferToPriorityOnUpdateOfTwoSiblings() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryUpdated(mGroupSummary)
+ mCollectionListener.onEntryUpdated(mGroupSibling1)
+ mCollectionListener.onEntryUpdated(mGroupSibling2)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ finishBind(mGroupSummary)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupPriority), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager).showNotification(mGroupSummary)
+ verify(mHeadsUpManager, never()).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testNoTransferTwoChildAlert_withGroupAlertSummary() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mCollectionListener.onEntryAdded(mGroupSibling2)
+ val groupEntry = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ finishBind(mGroupSummary)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager).showNotification(mGroupSummary)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testNoTransferTwoChildAlert_withGroupAlertAll() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mCollectionListener.onEntryAdded(mGroupChild2)
+ val groupEntry = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupChild1, mGroupChild2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ finishBind(mGroupSummary)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+ verify(mHeadsUpManager).showNotification(mGroupSummary)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ }
+
+ private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
+ whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
+ }
+
+ private fun finishBind(entry: NotificationEntry) {
+ verify(mHeadsUpManager, never()).showNotification(entry)
+ withArgCaptor<BindCallback> {
+ verify(mHeadsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+ }.onBindFinished(entry)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
new file mode 100644
index 0000000..d280f54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -0,0 +1,153 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationShelf
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for {@link NotificationShelf}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfTest : SysuiTestCase() {
+
+ private val shelf = NotificationShelf(context, /* attrs */ null)
+ private val shelfState = shelf.viewState as NotificationShelf.ShelfState
+ private val ambientState = mock(AmbientState::class.java)
+
+ @Before
+ fun setUp() {
+ shelf.bind(ambientState, /* hostLayoutController */ null)
+ shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+ }
+
+ @Test
+ fun testShadeWidth_BasedOnFractionToShade() {
+ setFractionToShade(0f)
+ setOnLockscreen(true)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 10)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 20)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 30)
+ }
+
+ @Test
+ fun testShelfIsLong_WhenNotOnLockscreen() {
+ setFractionToShade(0f)
+ setOnLockscreen(false)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 30)
+ }
+
+ @Test
+ fun testX_inViewForClick() {
+ val isXInView = shelf.isXInView(
+ /* localX */ 5f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isXInView)
+ }
+
+ @Test
+ fun testXSlop_inViewForClick() {
+ val isLeftXSlopInView = shelf.isXInView(
+ /* localX */ -3f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isLeftXSlopInView)
+
+ val isRightXSlopInView = shelf.isXInView(
+ /* localX */ 13f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isRightXSlopInView)
+ }
+
+ @Test
+ fun testX_notInViewForClick() {
+ val isXLeftOfShelfInView = shelf.isXInView(
+ /* localX */ -10f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertFalse(isXLeftOfShelfInView)
+
+ val isXRightOfShelfInView = shelf.isXInView(
+ /* localX */ 20f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertFalse(isXRightOfShelfInView)
+ }
+
+ @Test
+ fun testY_inViewForClick() {
+ val isYInView = shelf.isYInView(
+ /* localY */ 5f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isYInView)
+ }
+
+ @Test
+ fun testYSlop_inViewForClick() {
+ val isTopYSlopInView = shelf.isYInView(
+ /* localY */ -3f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isTopYSlopInView)
+
+ val isBottomYSlopInView = shelf.isYInView(
+ /* localY */ 13f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isBottomYSlopInView)
+ }
+
+ @Test
+ fun testY_notInViewForClick() {
+ val isYAboveShelfInView = shelf.isYInView(
+ /* localY */ -10f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 5f)
+ assertFalse(isYAboveShelfInView)
+
+ val isYBelowShelfInView = shelf.isYInView(
+ /* localY */ 15f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 5f)
+ assertFalse(isYBelowShelfInView)
+ }
+
+ private fun setFractionToShade(fraction: Float) {
+ shelf.setFractionToShade(fraction)
+ }
+
+ private fun setOnLockscreen(isOnLockscreen: Boolean) {
+ whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index ac32b9d..47f15a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -59,6 +59,13 @@
return createEntry(id, tag, true, groupAlertBehavior);
}
+ public NotificationEntry createSummaryNotification(
+ int groupAlertBehavior, int id, String tag, long when) {
+ NotificationEntry entry = createSummaryNotification(groupAlertBehavior, id, tag);
+ entry.getSbn().getNotification().when = when;
+ return entry;
+ }
+
public NotificationEntry createChildNotification() {
return createChildNotification(Notification.GROUP_ALERT_ALL);
}
@@ -71,6 +78,13 @@
return createEntry(id, tag, false, groupAlertBehavior);
}
+ public NotificationEntry createChildNotification(
+ int groupAlertBehavior, int id, String tag, long when) {
+ NotificationEntry entry = createChildNotification(groupAlertBehavior, id, tag);
+ entry.getSbn().getNotification().when = when;
+ return entry;
+ }
+
public NotificationEntry createEntry(int id, String tag, boolean isSummary,
int groupAlertBehavior) {
Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index c9462d6..b380553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -33,7 +33,6 @@
import android.media.session.MediaSession;
import android.os.Handler;
import android.os.Process;
-import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;
@@ -43,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -57,8 +57,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper
@@ -81,7 +79,7 @@
@Mock
private NotificationManager mNotificationManager;
@Mock
- private Vibrator mVibrator;
+ private VibratorHelper mVibrator;
@Mock
private IAudioService mIAudioService;
@Mock
@@ -110,7 +108,7 @@
mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
- mNotificationManager, Optional.of(mVibrator), mIAudioService, mAccessibilityManager,
+ mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
mPackageManager, mWakefullnessLifcycle, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -181,7 +179,7 @@
ThreadFactory theadFactory,
AudioManager audioManager,
NotificationManager notificationManager,
- Optional<Vibrator> optionalVibrator,
+ VibratorHelper optionalVibrator,
IAudioService iAudioService,
AccessibilityManager accessibilityManager,
PackageManager packageManager,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index aa42e8d..1cff374 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -284,7 +284,9 @@
}
final Session session = mSessions.get(sessionId);
if (session != null && uid == session.uid) {
- session.setAuthenticationResultLocked(data, authenticationId);
+ synchronized (session.mLock) {
+ session.setAuthenticationResultLocked(data, authenticationId);
+ }
}
}
@@ -374,7 +376,9 @@
+ " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly;
mMaster.logRequestLocked(historyItem);
- newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
+ synchronized (newSession.mLock) {
+ newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
+ }
if (forAugmentedAutofillOnly) {
// Must embed the flag in the response, at the high-end side of the long.
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a4bf52a..095c1fc 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -17,6 +17,7 @@
package com.android.server.autofill;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
@@ -149,9 +150,10 @@
private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
+ final Object mLock;
+
private final AutofillManagerServiceImpl mService;
private final Handler mHandler;
- private final Object mLock;
private final AutoFillUI mUi;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -428,6 +430,10 @@
/** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
@GuardedBy("mLock")
private boolean mClientSuggestionsEnabled;
+
+ /** Whether the fill dialog UI is disabled. */
+ @GuardedBy("mLock")
+ private boolean mFillDialogDisabled;
}
/**
@@ -860,7 +866,7 @@
mService.getRemoteInlineSuggestionRenderServiceLocked();
if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
&& remoteRenderService != null
- && isViewFocusedLocked(flags)) {
+ && (isViewFocusedLocked(flags) || (isRequestFromActivityStarted(flags)))) {
final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
if (mSessionFlags.mClientSuggestionsEnabled) {
final int finalRequestId = requestId;
@@ -906,6 +912,10 @@
requestAssistStructureLocked(requestId, flags);
}
+ private boolean isRequestFromActivityStarted(int flags) {
+ return (flags & FLAG_ACTIVITY_START) != 0;
+ }
+
@GuardedBy("mLock")
private void requestAssistStructureLocked(int requestId, int flags) {
try {
@@ -1501,6 +1511,23 @@
this, intentSender, intent));
}
+ // AutoFillUiCallback
+ @Override
+ public void requestShowSoftInput(AutofillId id) {
+ IAutoFillManagerClient client = getClient();
+ if (client != null) {
+ try {
+ client.requestShowSoftInput(id);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending input show up notification", e);
+ }
+ }
+ synchronized (Session.this.mLock) {
+ // stop to show fill dialog
+ mSessionFlags.mFillDialogDisabled = true;
+ }
+ }
+
private void notifyFillUiHidden(@NonNull AutofillId autofillId) {
synchronized (mLock) {
try {
@@ -2869,6 +2896,9 @@
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
+ if (!isRequestFromActivityStarted(flags)) {
+ mSessionFlags.mFillDialogDisabled = true;
+ }
requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
@@ -2958,6 +2988,7 @@
}
if (isSameViewEntered) {
+ setFillDialogDisabledAndStartInput();
return;
}
@@ -2968,6 +2999,7 @@
if (Objects.equals(mCurrentViewId, viewState.id)) {
if (sVerbose) Slog.v(TAG, "Exiting view " + id);
mUi.hideFillUi(this);
+ mUi.hideFillDialog(this);
hideAugmentedAutofillLocked(viewState);
// We don't send an empty response to IME so that it doesn't cause UI flicker
// on the IME side if it arrives before the input view is finished on the IME.
@@ -3148,6 +3180,17 @@
return;
}
+ if (requestShowFillDialog(response, filledId, filterText)) {
+ synchronized (mLock) {
+ final ViewState currentView = mViewStates.get(mCurrentViewId);
+ currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+ mService.logDatasetShown(id, mClientState);
+ }
+ return;
+ }
+
+ setFillDialogDisabled();
+
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -3192,6 +3235,81 @@
}
}
+ @GuardedBy("mLock")
+ private void updateFillDialogTriggerIdsLocked() {
+ final FillResponse response = getLastResponseLocked(null);
+
+ if (response == null) return;
+
+ final AutofillId[] ids = response.getFillDialogTriggerIds();
+ notifyClientFillDialogTriggerIds(ids == null ? null : Arrays.asList(ids));
+ }
+
+ private void notifyClientFillDialogTriggerIds(List<AutofillId> fieldIds) {
+ try {
+ if (sVerbose) {
+ Slog.v(TAG, "notifyFillDialogTriggerIds(): " + fieldIds);
+ }
+ getClient().notifyFillDialogTriggerIds(fieldIds);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Cannot set trigger ids for fill dialog", e);
+ }
+ }
+
+ private boolean isFillDialogUiEnabled() {
+ // TODO read from Settings or somewhere
+ final boolean isSettingsEnabledFillDialog = true;
+ synchronized (Session.this.mLock) {
+ return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
+ }
+ }
+
+ private void setFillDialogDisabled() {
+ synchronized (mLock) {
+ mSessionFlags.mFillDialogDisabled = true;
+ }
+ notifyClientFillDialogTriggerIds(null);
+ }
+
+ private void setFillDialogDisabledAndStartInput() {
+ if (getUiForShowing().isFillDialogShowing()) {
+ setFillDialogDisabled();
+ final AutofillId id;
+ synchronized (mLock) {
+ id = mCurrentViewId;
+ }
+ requestShowSoftInput(id);
+ }
+ }
+
+ private boolean requestShowFillDialog(FillResponse response,
+ AutofillId filledId, String filterText) {
+ if (!isFillDialogUiEnabled()) {
+ // Unsupported fill dialog UI
+ return false;
+ }
+
+ final AutofillId[] ids = response.getFillDialogTriggerIds();
+ if (ids == null || !ArrayUtils.contains(ids, filledId)) {
+ return false;
+ }
+
+ final Drawable serviceIcon = getServiceIcon();
+
+ getUiForShowing().showFillDialog(filledId, response, filterText,
+ mService.getServicePackageName(), mComponentName, serviceIcon, this);
+ return true;
+ }
+
+ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
+ // actually the same object as mLock.
+ // TODO: Expose mService.mLock or redesign instead.
+ private Drawable getServiceIcon() {
+ synchronized (mLock) {
+ return mService.getServiceIconLocked();
+ }
+ }
+
/**
* Returns whether we made a request to show inline suggestions.
*/
@@ -3584,9 +3702,10 @@
mService.getRemoteInlineSuggestionRenderServiceLocked();
if (remoteRenderService != null
&& (mSessionFlags.mAugmentedAutofillOnly
- || !mSessionFlags.mInlineSupportedByService
- || mSessionFlags.mExpiredResponse)
- && isViewFocusedLocked(flags)) {
+ || !mSessionFlags.mInlineSupportedByService
+ || mSessionFlags.mExpiredResponse)
+ && isViewFocusedLocked(flags)
+ || isFillDialogUiEnabled()) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
(extras) -> {
@@ -3642,6 +3761,7 @@
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
+ updateFillDialogTriggerIdsLocked();
updateTrackedIdsLocked();
if (mCurrentViewId == null) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index adb1e3e..4a14f14 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -82,6 +82,8 @@
public static final int STATE_INLINE_DISABLED = 0x8000;
/** The View is waiting for an inline suggestions request from IME.*/
public static final int STATE_PENDING_CREATE_INLINE_REQUEST = 0x10000;
+ /** Fill dialog were shown for this View. */
+ public static final int STATE_FILL_DIALOG_SHOWN = 0x20000;
public final AutofillId id;
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 71c3c16..056ab92 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -66,6 +66,7 @@
private @Nullable FillUi mFillUi;
private @Nullable SaveUi mSaveUi;
+ private @Nullable DialogFillUi mFillDialog;
private @Nullable AutoFillUiCallback mCallback;
@@ -90,6 +91,7 @@
void startIntentSender(IntentSender intentSender, Intent intent);
void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
void cancelSession();
+ void requestShowSoftInput(AutofillId id);
}
public AutoFillUI(@NonNull Context context) {
@@ -155,6 +157,12 @@
}
/**
+ * Hides the fill UI.
+ */
+ public void hideFillDialog(@NonNull AutoFillUiCallback callback) {
+ mHandler.post(() -> hideFillDialogUiThread(callback));
+ }
+ /**
* Filters the options in the fill UI.
*
* @param filterText The filter prefix.
@@ -369,6 +377,62 @@
}
/**
+ * Shows the UI asking the user to choose for autofill.
+ */
+ public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
+ @Nullable String filterText, @Nullable String servicePackageName,
+ @NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
+ @NonNull AutoFillUiCallback callback) {
+ if (sVerbose) {
+ Slog.v(TAG, "showFillDialog for "
+ + componentName.toShortString() + ": " + response);
+ }
+
+ // TODO: enable LogMaker
+
+ mHandler.post(() -> {
+ if (callback != mCallback) {
+ return;
+ }
+ hideAllUiThread(callback);
+ mFillDialog = new DialogFillUi(mContext, response, focusedId, filterText,
+ serviceIcon, servicePackageName, componentName, mOverlayControl,
+ mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
+ @Override
+ public void onResponsePicked(FillResponse response) {
+ hideFillDialogUiThread(callback);
+ if (mCallback != null) {
+ mCallback.authenticate(response.getRequestId(),
+ AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
+ response.getAuthentication(), response.getClientState(),
+ /* authenticateInline= */ false);
+ }
+ }
+
+ @Override
+ public void onDatasetPicked(Dataset dataset) {
+ hideFillDialogUiThread(callback);
+ if (mCallback != null) {
+ final int datasetIndex = response.getDatasets().indexOf(dataset);
+ mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+ }
+ }
+
+ @Override
+ public void onCanceled() {
+ hideFillDialogUiThread(callback);
+ callback.requestShowSoftInput(focusedId);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intentSender) {
+ mCallback.startIntentSenderAndFinishSession(intentSender);
+ }
+ });
+ });
+ }
+
+ /**
* Executes an operation in the pending save UI, if any.
*/
public void onPendingSaveUi(int operation, @NonNull IBinder token) {
@@ -400,6 +464,10 @@
return mSaveUi == null ? false : mSaveUi.isShowing();
}
+ public boolean isFillDialogShowing() {
+ return mFillDialog == null ? false : mFillDialog.isShowing();
+ }
+
public void dump(PrintWriter pw) {
pw.println("Autofill UI");
final String prefix = " ";
@@ -417,6 +485,12 @@
} else {
pw.print(prefix); pw.println("showsSaveUi: false");
}
+ if (mFillDialog != null) {
+ pw.print(prefix); pw.println("showsFillDialog: true");
+ mFillDialog.dump(pw, prefix2);
+ } else {
+ pw.print(prefix); pw.println("showsFillDialog: false");
+ }
}
@android.annotation.UiThread
@@ -442,6 +516,14 @@
}
@android.annotation.UiThread
+ private void hideFillDialogUiThread(@Nullable AutoFillUiCallback callback) {
+ if (mFillDialog != null && (callback == null || callback == mCallback)) {
+ mFillDialog.destroy();
+ mFillDialog = null;
+ }
+ }
+
+ @android.annotation.UiThread
private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
if (mSaveUi == null) {
// Calling destroySaveUiUiThread() twice is normal - it usually happens when the
@@ -475,12 +557,14 @@
private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
@Nullable AutoFillUiCallback callback, boolean notifyClient) {
hideFillUiUiThread(callback, notifyClient);
+ hideFillDialogUiThread(callback);
destroySaveUiUiThread(pendingSaveUi, notifyClient);
}
@android.annotation.UiThread
private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
hideFillUiUiThread(callback, true);
+ hideFillDialogUiThread(callback);
final PendingUi pendingSaveUi = hideSaveUiUiThread(callback);
if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) {
if (sDebug) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
new file mode 100644
index 0000000..e122993
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -0,0 +1,629 @@
+/*
+ * 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.autofill.ui;
+
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentSender;
+import android.graphics.drawable.Drawable;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.text.TextUtils;
+import android.util.PluralsMessageFormatter;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.server.autofill.AutofillManagerService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * A dialog to show Autofill suggestions.
+ *
+ * This fill dialog UI shows as a bottom sheet style dialog. This dialog UI
+ * provides a larger area to display the suggestions, it provides a more
+ * conspicuous and efficient interface to the user. So it is easy for users
+ * to pay attention to the datasets and selecting one of them.
+ */
+final class DialogFillUi {
+
+ private static final String TAG = "DialogFillUi";
+ private static final int THEME_ID_LIGHT =
+ R.style.Theme_DeviceDefault_Light_Autofill_Save;
+ private static final int THEME_ID_DARK =
+ R.style.Theme_DeviceDefault_Autofill_Save;
+
+ interface UiCallback {
+ void onResponsePicked(@NonNull FillResponse response);
+ void onDatasetPicked(@NonNull Dataset dataset);
+ void onCanceled();
+ void startIntentSender(IntentSender intentSender);
+ }
+
+ private final @NonNull Dialog mDialog;
+ private final @NonNull OverlayControl mOverlayControl;
+ private final String mServicePackageName;
+ private final ComponentName mComponentName;
+ private final int mThemeId;
+ private final @NonNull Context mContext;
+ private final @NonNull UiCallback mCallback;
+ private final @NonNull ListView mListView;
+ private final @Nullable ItemsAdapter mAdapter;
+ private final int mVisibleDatasetsMaxCount;
+
+ private @Nullable String mFilterText;
+ private @Nullable AnnounceFilterResult mAnnounceFilterResult;
+ private boolean mDestroyed;
+
+ DialogFillUi(@NonNull Context context, @NonNull FillResponse response,
+ @NonNull AutofillId focusedViewId, @Nullable String filterText,
+ @Nullable Drawable serviceIcon, @Nullable String servicePackageName,
+ @Nullable ComponentName componentName, @NonNull OverlayControl overlayControl,
+ boolean nightMode, @NonNull UiCallback callback) {
+ if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
+ mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
+ mCallback = callback;
+ mOverlayControl = overlayControl;
+ mServicePackageName = servicePackageName;
+ mComponentName = componentName;
+
+ mContext = new ContextThemeWrapper(context, mThemeId);
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null);
+
+ setServiceIcon(decor, serviceIcon);
+ setHeader(decor, response);
+
+ mVisibleDatasetsMaxCount = getVisibleDatasetsMaxCount();
+
+ if (response.getAuthentication() != null) {
+ mListView = null;
+ mAdapter = null;
+ try {
+ initialAuthenticationLayout(decor, response);
+ } catch (RuntimeException e) {
+ callback.onCanceled();
+ Slog.e(TAG, "Error inflating remote views", e);
+ mDialog = null;
+ return;
+ }
+ } else {
+ final List<ViewItem> items = createDatasetItems(response, focusedViewId);
+ mAdapter = new ItemsAdapter(items);
+ mListView = decor.findViewById(R.id.autofill_dialog_list);
+ initialDatasetLayout(decor, filterText);
+ }
+
+ setDismissButton(decor);
+
+ mDialog = new Dialog(mContext, mThemeId);
+ mDialog.setContentView(decor);
+ setDialogParamsAsBottomSheet();
+
+ show();
+ }
+
+ private int getVisibleDatasetsMaxCount() {
+ if (AutofillManagerService.getVisibleDatasetsMaxCount() > 0) {
+ final int maxCount = AutofillManagerService.getVisibleDatasetsMaxCount();
+ if (sVerbose) {
+ Slog.v(TAG, "overriding maximum visible datasets to " + maxCount);
+ }
+ return maxCount;
+ } else {
+ return mContext.getResources()
+ .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
+ }
+ }
+
+ private void setDialogParamsAsBottomSheet() {
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
+ window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+ window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
+ window.setCloseOnTouchOutside(true);
+ final WindowManager.LayoutParams params = window.getAttributes();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.accessibilityTitle =
+ mContext.getString(R.string.autofill_picker_accessibility_title);
+ params.windowAnimations = R.style.AutofillSaveAnimation;
+ }
+
+ private void setServiceIcon(View decor, Drawable serviceIcon) {
+ if (serviceIcon == null) {
+ return;
+ }
+
+ final ImageView iconView = decor.findViewById(R.id.autofill_service_icon);
+ final int actualWidth = serviceIcon.getMinimumWidth();
+ final int actualHeight = serviceIcon.getMinimumHeight();
+ if (sDebug) {
+ Slog.d(TAG, "Adding service icon "
+ + "(" + actualWidth + "x" + actualHeight + ")");
+ }
+ iconView.setImageDrawable(serviceIcon);
+ iconView.setVisibility(View.VISIBLE);
+ }
+
+ private void setHeader(View decor, FillResponse response) {
+ final RemoteViews presentation = response.getDialogHeader();
+ if (presentation == null) {
+ return;
+ }
+
+ final ViewGroup container = decor.findViewById(R.id.autofill_dialog_header);
+ final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+ if (pendingIntent != null) {
+ mCallback.startIntentSender(pendingIntent.getIntentSender());
+ }
+ return true;
+ };
+
+ final View content = presentation.applyWithTheme(
+ mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+ container.addView(content);
+ container.setVisibility(View.VISIBLE);
+ }
+
+ private void setDismissButton(View decor) {
+ final TextView noButton = decor.findViewById(R.id.autofill_dialog_no);
+ noButton.setOnClickListener((v) -> mCallback.onCanceled());
+ }
+
+ private void setContinueButton(View decor, View.OnClickListener listener) {
+ final TextView yesButton = decor.findViewById(R.id.autofill_dialog_yes);
+ // set "Continue" by default
+ yesButton.setText(R.string.autofill_continue_yes);
+ yesButton.setOnClickListener(listener);
+ }
+
+ private void initialAuthenticationLayout(View decor, FillResponse response) {
+ RemoteViews presentation = response.getDialogPresentation();
+ if (presentation == null) {
+ presentation = response.getPresentation();
+ }
+ if (presentation == null) {
+ throw new RuntimeException("No presentation for fill dialog authentication");
+ }
+
+ // insert authentication item under autofill_dialog_container
+ final ViewGroup container = decor.findViewById(R.id.autofill_dialog_container);
+ final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+ if (pendingIntent != null) {
+ mCallback.startIntentSender(pendingIntent.getIntentSender());
+ }
+ return true;
+ };
+ final View content = presentation.applyWithTheme(
+ mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+ container.addView(content);
+ container.setVisibility(View.VISIBLE);
+ container.setFocusable(true);
+ container.setOnClickListener(v -> mCallback.onResponsePicked(response));
+ // just single item, set up continue button
+ setContinueButton(decor, v -> mCallback.onResponsePicked(response));
+ }
+
+ private ArrayList<ViewItem> createDatasetItems(FillResponse response,
+ AutofillId focusedViewId) {
+ final int datasetCount = response.getDatasets().size();
+ if (sVerbose) {
+ Slog.v(TAG, "Number datasets: " + datasetCount + " max visible: "
+ + mVisibleDatasetsMaxCount);
+ }
+
+ final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+ if (pendingIntent != null) {
+ mCallback.startIntentSender(pendingIntent.getIntentSender());
+ }
+ return true;
+ };
+
+ final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
+ for (int i = 0; i < datasetCount; i++) {
+ final Dataset dataset = response.getDatasets().get(i);
+ final int index = dataset.getFieldIds().indexOf(focusedViewId);
+ if (index >= 0) {
+ RemoteViews presentation = dataset.getFieldDialogPresentation(index);
+ if (presentation == null) {
+ Slog.w(TAG, "fallback to presentation");
+ presentation = dataset.getFieldPresentation(index);
+ }
+ if (presentation == null) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ + "service didn't provide a presentation for it on " + dataset);
+ continue;
+ }
+ final View view;
+ try {
+ if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
+ view = presentation.applyWithTheme(
+ mContext, null, interceptionHandler, mThemeId);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Error inflating remote views", e);
+ continue;
+ }
+ // TODO: Extract the shared filtering logic here and in FillUi to a common
+ // method.
+ final Dataset.DatasetFieldFilter filter = dataset.getFilter(index);
+ Pattern filterPattern = null;
+ String valueText = null;
+ boolean filterable = true;
+ if (filter == null) {
+ final AutofillValue value = dataset.getFieldValues().get(index);
+ if (value != null && value.isText()) {
+ valueText = value.getTextValue().toString().toLowerCase();
+ }
+ } else {
+ filterPattern = filter.pattern;
+ if (filterPattern == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "Explicitly disabling filter at id " + focusedViewId
+ + " for dataset #" + index);
+ }
+ filterable = false;
+ }
+ }
+
+ items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view));
+ }
+ }
+ return items;
+ }
+
+ private void initialDatasetLayout(View decor, String filterText) {
+ final AdapterView.OnItemClickListener onItemClickListener =
+ (adapter, view, position, id) -> {
+ final ViewItem vi = mAdapter.getItem(position);
+ mCallback.onDatasetPicked(vi.dataset);
+ };
+
+ mListView.setAdapter(mAdapter);
+ mListView.setVisibility(View.VISIBLE);
+ mListView.setOnItemClickListener(onItemClickListener);
+
+ if (mAdapter.getCount() == 1) {
+ // just single item, set up continue button
+ setContinueButton(decor, (v) ->
+ onItemClickListener.onItemClick(null, null, 0, 0));
+ }
+
+ if (filterText == null) {
+ mFilterText = null;
+ } else {
+ mFilterText = filterText.toLowerCase();
+ }
+
+ final int oldCount = mAdapter.getCount();
+ mAdapter.getFilter().filter(mFilterText, (count) -> {
+ if (mDestroyed) {
+ return;
+ }
+ if (count <= 0) {
+ if (sDebug) {
+ final int size = mFilterText == null ? 0 : mFilterText.length();
+ Slog.d(TAG, "No dataset matches filter with " + size + " chars");
+ }
+ mCallback.onCanceled();
+ } else {
+
+ if (mAdapter.getCount() > mVisibleDatasetsMaxCount) {
+ mListView.setVerticalScrollBarEnabled(true);
+ mListView.onVisibilityAggregated(true);
+ } else {
+ mListView.setVerticalScrollBarEnabled(false);
+ }
+ if (mAdapter.getCount() != oldCount) {
+ mListView.requestLayout();
+ }
+ }
+ });
+ }
+
+ private void show() {
+ Slog.i(TAG, "Showing fill dialog");
+ mDialog.show();
+ mOverlayControl.hideOverlays();
+ }
+
+ boolean isShowing() {
+ return mDialog.isShowing();
+ }
+
+ void hide() {
+ if (sVerbose) Slog.v(TAG, "Hiding fill dialog.");
+ try {
+ mDialog.hide();
+ } finally {
+ mOverlayControl.showOverlays();
+ }
+ }
+
+ void destroy() {
+ try {
+ if (sDebug) Slog.d(TAG, "destroy()");
+ throwIfDestroyed();
+
+ mDialog.dismiss();
+ mDestroyed = true;
+ } finally {
+ mOverlayControl.showOverlays();
+ }
+ }
+
+ private void throwIfDestroyed() {
+ if (mDestroyed) {
+ throw new IllegalStateException("cannot interact with a destroyed instance");
+ }
+ }
+
+ @Override
+ public String toString() {
+ // TODO toString
+ return "NO TITLE";
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+
+ pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+ pw.print(prefix); pw.print("app: "); pw.println(mComponentName.toShortString());
+ pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
+ switch (mThemeId) {
+ case THEME_ID_DARK:
+ pw.println(" (dark)");
+ break;
+ case THEME_ID_LIGHT:
+ pw.println(" (light)");
+ break;
+ default:
+ pw.println("(UNKNOWN_MODE)");
+ break;
+ }
+ final View view = mDialog.getWindow().getDecorView();
+ final int[] loc = view.getLocationOnScreen();
+ pw.print(prefix); pw.print("coordinates: ");
+ pw.print('('); pw.print(loc[0]); pw.print(','); pw.print(loc[1]); pw.print(')');
+ pw.print('(');
+ pw.print(loc[0] + view.getWidth()); pw.print(',');
+ pw.print(loc[1] + view.getHeight()); pw.println(')');
+ pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+ }
+
+ private void announceSearchResultIfNeeded() {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (mAnnounceFilterResult == null) {
+ mAnnounceFilterResult = new AnnounceFilterResult();
+ }
+ mAnnounceFilterResult.post();
+ }
+ }
+
+ // TODO: Below code copied from FullUi, Extract the shared filtering logic here
+ // and in FillUi to a common method.
+ private final class AnnounceFilterResult implements Runnable {
+ private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
+
+ public void post() {
+ remove();
+ mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
+ }
+
+ public void remove() {
+ mListView.removeCallbacks(this);
+ }
+
+ @Override
+ public void run() {
+ final int count = mListView.getAdapter().getCount();
+ final String text;
+ if (count <= 0) {
+ text = mContext.getString(R.string.autofill_picker_no_suggestions);
+ } else {
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", count);
+ text = PluralsMessageFormatter.format(mContext.getResources(),
+ arguments,
+ R.string.autofill_picker_some_suggestions);
+ }
+ mListView.announceForAccessibility(text);
+ }
+ }
+
+ private final class ItemsAdapter extends BaseAdapter implements Filterable {
+ private @NonNull final List<ViewItem> mAllItems;
+
+ private @NonNull final List<ViewItem> mFilteredItems = new ArrayList<>();
+
+ ItemsAdapter(@NonNull List<ViewItem> items) {
+ mAllItems = Collections.unmodifiableList(new ArrayList<>(items));
+ mFilteredItems.addAll(items);
+ }
+
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ protected FilterResults performFiltering(CharSequence filterText) {
+ // No locking needed as mAllItems is final an immutable
+ final List<ViewItem> filtered = mAllItems.stream()
+ .filter((item) -> item.matches(filterText))
+ .collect(Collectors.toList());
+ final FilterResults results = new FilterResults();
+ results.values = filtered;
+ results.count = filtered.size();
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ final boolean resultCountChanged;
+ final int oldItemCount = mFilteredItems.size();
+ mFilteredItems.clear();
+ if (results.count > 0) {
+ @SuppressWarnings("unchecked") final List<ViewItem> items =
+ (List<ViewItem>) results.values;
+ mFilteredItems.addAll(items);
+ }
+ resultCountChanged = (oldItemCount != mFilteredItems.size());
+ if (resultCountChanged) {
+ announceSearchResultIfNeeded();
+ }
+ notifyDataSetChanged();
+ }
+ };
+ }
+
+ @Override
+ public int getCount() {
+ return mFilteredItems.size();
+ }
+
+ @Override
+ public ViewItem getItem(int position) {
+ return mFilteredItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return getItem(position).view;
+ }
+
+ @Override
+ public String toString() {
+ return "ItemsAdapter: [all=" + mAllItems + ", filtered=" + mFilteredItems + "]";
+ }
+ }
+
+
+ /**
+ * An item for the list view - either a (clickable) dataset or a (read-only) header / footer.
+ */
+ private static class ViewItem {
+ public final @Nullable String value;
+ public final @Nullable Dataset dataset;
+ public final @NonNull View view;
+ public final @Nullable Pattern filter;
+ public final boolean filterable;
+
+ /**
+ * Default constructor.
+ *
+ * @param dataset dataset associated with the item
+ * @param filter optional filter set by the service to determine how the item should be
+ * filtered
+ * @param filterable optional flag set by the service to indicate this item should not be
+ * filtered (typically used when the dataset has value but it's sensitive, like a password)
+ * @param value dataset value
+ * @param view dataset presentation.
+ */
+ ViewItem(@NonNull Dataset dataset, @Nullable Pattern filter, boolean filterable,
+ @Nullable String value, @NonNull View view) {
+ this.dataset = dataset;
+ this.value = value;
+ this.view = view;
+ this.filter = filter;
+ this.filterable = filterable;
+ }
+
+ /**
+ * Returns whether this item matches the value input by the user so it can be included
+ * in the filtered datasets.
+ */
+ public boolean matches(CharSequence filterText) {
+ if (TextUtils.isEmpty(filterText)) {
+ // Always show item when the user input is empty
+ return true;
+ }
+ if (!filterable) {
+ // Service explicitly disabled filtering using a null Pattern.
+ return false;
+ }
+ final String constraintLowerCase = filterText.toString().toLowerCase();
+ if (filter != null) {
+ // Uses pattern provided by service
+ return filter.matcher(constraintLowerCase).matches();
+ } else {
+ // Compares it with dataset value with dataset
+ return (value == null)
+ ? (dataset.getAuthentication() == null)
+ : value.toLowerCase().startsWith(constraintLowerCase);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("ViewItem:[view=")
+ .append(view.getAutofillId());
+ final String datasetId = dataset == null ? null : dataset.getId();
+ if (datasetId != null) {
+ builder.append(", dataset=").append(datasetId);
+ }
+ if (value != null) {
+ // Cannot print value because it could contain PII
+ builder.append(", value=").append(value.length()).append("_chars");
+ }
+ if (filterable) {
+ builder.append(", filterable");
+ }
+ if (filter != null) {
+ // Filter should not have PII, but it could be a huge regexp
+ builder.append(", filter=").append(filter.pattern().length()).append("_chars");
+ }
+ return builder.append(']').toString();
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index cfd3798..c3ab2a7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1255,7 +1255,7 @@
}
@Override
- public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {
+ public void onDeviceDisconnected(BluetoothDevice device, int reason) {
Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") "
+ BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason));
CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index dbe866b..93cbe97 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -91,7 +91,7 @@
*/
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
- @DisconnectReason int reason) {
+ int reason) {
if (DEBUG) {
Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
Log.d(TAG, " reason=" + disconnectReasonText(reason));
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 6c56e2f..e6bfd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -18,8 +18,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.graphics.Point;
import android.graphics.PointF;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -48,6 +51,20 @@
private static final String TAG = "VirtualInputController";
+ private static final AtomicLong sNextPhysId = new AtomicLong(1);
+
+ static final String PHYS_TYPE_KEYBOARD = "Keyboard";
+ static final String PHYS_TYPE_MOUSE = "Mouse";
+ static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+ @StringDef(prefix = { "PHYS_TYPE_" }, value = {
+ PHYS_TYPE_KEYBOARD,
+ PHYS_TYPE_MOUSE,
+ PHYS_TYPE_TOUCHSCREEN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PhysType {
+ }
+
private final Object mLock;
/* Token -> file descriptor associations. */
@@ -56,6 +73,8 @@
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
private final NativeWrapper mNativeWrapper;
+ private final DisplayManagerInternal mDisplayManagerInternal;
+ private final InputManagerInternal mInputManagerInternal;
/**
* Because the pointer is a singleton, it can only be targeted at one display at a time. Because
@@ -73,6 +92,8 @@
mLock = lock;
mNativeWrapper = nativeWrapper;
mActivePointerDisplayId = Display.INVALID_DISPLAY;
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
}
void close() {
@@ -90,7 +111,9 @@
int productId,
@NonNull IBinder deviceToken,
int displayId) {
- final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+ final String phys = createPhys(PHYS_TYPE_KEYBOARD);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
if (fd < 0) {
throw new RuntimeException(
"A native error occurred when creating keyboard: " + -fd);
@@ -99,7 +122,7 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_KEYBOARD, displayId));
+ InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
}
try {
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -114,7 +137,9 @@
int productId,
@NonNull IBinder deviceToken,
int displayId) {
- final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+ final String phys = createPhys(PHYS_TYPE_MOUSE);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
if (fd < 0) {
throw new RuntimeException(
"A native error occurred when creating mouse: " + -fd);
@@ -123,11 +148,9 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_MOUSE, displayId));
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
- inputManagerInternal.setPointerAcceleration(1);
+ InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ mInputManagerInternal.setPointerAcceleration(1);
mActivePointerDisplayId = displayId;
}
try {
@@ -144,7 +167,9 @@
@NonNull IBinder deviceToken,
int displayId,
@NonNull Point screenSize) {
- final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
screenSize.y, screenSize.x);
if (fd < 0) {
throw new RuntimeException(
@@ -154,7 +179,7 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId));
+ InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
}
try {
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -174,6 +199,7 @@
}
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+ InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
// Reset values to the default if all virtual mice are unregistered, or set display
// id if there's another mouse (choose the most recent).
@@ -197,9 +223,7 @@
}
}
if (mostRecentlyCreatedMouse != null) {
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(
+ mInputManagerInternal.setVirtualMousePointerDisplayId(
mostRecentlyCreatedMouse.getDisplayId());
mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId();
} else {
@@ -209,14 +233,21 @@
}
private void resetMouseValuesLocked() {
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
- inputManagerInternal.setPointerAcceleration(
+ mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+ mInputManagerInternal.setPointerAcceleration(
IInputConstants.DEFAULT_POINTER_ACCELERATION);
mActivePointerDisplayId = Display.INVALID_DISPLAY;
}
+ private static String createPhys(@PhysType String type) {
+ return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+ }
+
+ private void setUniqueIdAssociation(int displayId, String phys) {
+ final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
+ InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+ }
+
boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
synchronized (mLock) {
final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
@@ -321,17 +352,18 @@
fout.println(" creationOrder: "
+ inputDeviceDescriptor.getCreationOrderNumber());
fout.println(" type: " + inputDeviceDescriptor.getType());
+ fout.println(" phys: " + inputDeviceDescriptor.getPhys());
}
fout.println(" Active mouse display id: " + mActivePointerDisplayId);
}
}
private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
- int productId);
- private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
- int productId);
+ int productId, String phys);
+ private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
+ String phys);
private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
- int productId, int height, int width);
+ int productId, String phys, int height, int width);
private static native boolean nativeCloseUinput(int fd);
private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
@@ -345,20 +377,18 @@
/** Wrapper around the static native methods for tests. */
@VisibleForTesting
protected static class NativeWrapper {
- public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
- return nativeOpenUinputKeyboard(deviceName, vendorId,
- productId);
+ public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
+ return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
}
- public int openUinputMouse(String deviceName, int vendorId, int productId) {
- return nativeOpenUinputMouse(deviceName, vendorId,
- productId);
+ public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
+ return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
}
- public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
- int width) {
- return nativeOpenUinputTouchscreen(deviceName, vendorId,
- productId, height, width);
+ public int openUinputTouchscreen(String deviceName, int vendorId,
+ int productId, String phys, int height, int width) {
+ return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
+ width);
}
public boolean closeUinput(int fd) {
@@ -410,15 +440,17 @@
private final IBinder.DeathRecipient mDeathRecipient;
private final @Type int mType;
private final int mDisplayId;
+ private final String mPhys;
// Monotonically increasing number; devices with lower numbers were created earlier.
private final long mCreationOrderNumber;
- InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient,
- @Type int type, int displayId) {
+ InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
+ int displayId, String phys) {
mFd = fd;
mDeathRecipient = deathRecipient;
mType = type;
mDisplayId = displayId;
+ mPhys = phys;
mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
}
@@ -445,6 +477,10 @@
public long getCreationOrderNumber() {
return mCreationOrderNumber;
}
+
+ public String getPhys() {
+ return mPhys;
+ }
}
private final class BinderDeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 16645df..06c11fa 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -673,6 +673,12 @@
throw new UnsupportedOperationException("cannot read frp credential");
}
}
+
+ @Override
+ public String getPersistentDataPackageName() {
+ enforcePersistentDataBlockAccess();
+ return mContext.getString(R.string.config_persistentDataPackageName);
+ }
};
private PersistentDataBlockManagerInternal mInternalService =
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 8a21a0f..14d73f6 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -145,7 +145,7 @@
/**
* The uid battery usage stats data from our last query, it does not include snapshot data.
*/
- // No lock is needed.
+ @GuardedBy("mLock")
private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray();
// No lock is needed.
@@ -155,12 +155,15 @@
private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray();
// No lock is needed.
+ private final SparseDoubleArray mTmpUidBatteryUsageInWindow = new SparseDoubleArray();
+
+ // No lock is needed.
private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
/**
* The start timestamp of the battery usage stats result from our last query.
*/
- // No lock is needed.
+ @GuardedBy("mLock")
private long mLastUidBatteryUsageStartTs;
// For debug only.
@@ -296,8 +299,10 @@
checkBatteryUsageStats();
} else {
// We didn't do the battery stats update above, schedule a check later.
- scheduleBatteryUsageStatsUpdateIfNecessary(
- mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+ synchronized (mLock) {
+ scheduleBatteryUsageStatsUpdateIfNecessary(
+ mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+ }
}
}
@@ -305,7 +310,10 @@
final long now = SystemClock.elapsedRealtime();
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
try {
- final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+ final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow;
+ synchronized (mLock) {
+ copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers);
+ }
final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
for (int i = 0, size = uidConsumers.size(); i < size; i++) {
final int uid = uidConsumers.keyAt(i);
@@ -408,7 +416,9 @@
if (curDuration >= windowSize) {
// If we do have long enough data for the window, save it.
- copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ synchronized (mLock) {
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ }
needUpdateUidBatteryUsageInWindow = false;
}
@@ -416,8 +426,11 @@
mTmpUidBatteryUsage2.clear();
copyUidBatteryUsage(buf, mTmpUidBatteryUsage2);
- final long lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
- mLastUidBatteryUsageStartTs = curStart;
+ final long lastUidBatteryUsageStartTs;
+ synchronized (mLock) {
+ lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
+ mLastUidBatteryUsageStartTs = curStart;
+ }
if (curStart > lastUidBatteryUsageStartTs && lastUidBatteryUsageStartTs > 0) {
// The battery usage stats committed data since our last query,
// let's query the snapshots to get the data since last start.
@@ -429,42 +442,47 @@
}
if (needUpdateUidBatteryUsageInWindow && curDuration > windowSize) {
// If we do have long enough data for the window, save it.
- copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ synchronized (mLock) {
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ }
needUpdateUidBatteryUsageInWindow = false;
}
// Add the delta into the global records.
- for (int i = 0, size = buf.size(); i < size; i++) {
- final int uid = buf.keyAt(i);
- final int index = mUidBatteryUsage.indexOfKey(uid);
- final double delta = Math.max(0.0d,
- buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d));
- final double before;
- if (index >= 0) {
- before = mUidBatteryUsage.valueAt(index);
- mUidBatteryUsage.setValueAt(index, before + delta);
- } else {
- before = 0.0d;
- mUidBatteryUsage.put(uid, delta);
- }
- if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
- final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d);
- String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
- + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + ", delta=" + actualDelta
- + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d)
- + ", curStart=" + curStart
- + ", lastLastStart=" + lastUidBatteryUsageStartTs
- + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
- if (actualDelta < 0.0d) {
- // Something is wrong, the battery usage shouldn't be negative.
- Slog.e(TAG, msg);
+ synchronized (mLock) {
+ for (int i = 0, size = buf.size(); i < size; i++) {
+ final int uid = buf.keyAt(i);
+ final int index = mUidBatteryUsage.indexOfKey(uid);
+ final double delta = Math.max(0.0d,
+ buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d));
+ final double before;
+ if (index >= 0) {
+ before = mUidBatteryUsage.valueAt(index);
+ mUidBatteryUsage.setValueAt(index, before + delta);
} else {
- Slog.i(TAG, msg);
+ before = 0.0d;
+ mUidBatteryUsage.put(uid, delta);
+ }
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d);
+ String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
+ + ", after=" + mUidBatteryUsage.get(uid, 0.0d)
+ + ", delta=" + actualDelta
+ + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d)
+ + ", curStart=" + curStart
+ + ", lastLastStart=" + lastUidBatteryUsageStartTs
+ + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
+ if (actualDelta < 0.0d) {
+ // Something is wrong, the battery usage shouldn't be negative.
+ Slog.e(TAG, msg);
+ } else {
+ Slog.i(TAG, msg);
+ }
}
}
+ // Now update the mLastUidBatteryUsage with the data we just saved above.
+ copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
}
- // Now update the mLastUidBatteryUsage with the data we just saved above.
- copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
mTmpUidBatteryUsage2.clear();
if (needUpdateUidBatteryUsageInWindow) {
@@ -473,7 +491,9 @@
.includeProcessStateData()
.aggregateSnapshots(now - windowSize, lastUidBatteryUsageStartTs);
updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal);
- copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
+ synchronized (mLock) {
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
+ }
}
}
@@ -584,38 +604,40 @@
pw.print(prefix);
pw.println("APP BATTERY STATE TRACKER:");
updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
- final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
- pw.print(" " + prefix);
- pw.print("Boot=");
- TimeUtils.dumpTime(pw, mBootTimestamp);
- pw.print(" Last battery usage start=");
- TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
- pw.println();
- pw.print(" " + prefix);
- pw.print("Battery usage over last ");
- final String newPrefix = " " + prefix;
- final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
- final long now = SystemClock.elapsedRealtime();
- final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
- pw.println(TimeUtils.formatDuration(now - since));
- if (uidConsumers.size() == 0) {
- pw.print(newPrefix);
- pw.println("(none)");
- } else {
- for (int i = 0, size = uidConsumers.size(); i < size; i++) {
- final int uid = uidConsumers.keyAt(i);
- final double bgUsage = uidConsumers.valueAt(i);
- final double exemptedUsage = mAppRestrictionController
- .getUidBatteryExemptedUsageSince(uid, since, now);
- final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
- pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
- + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
- newPrefix, UserHandle.formatUid(uid),
- PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
- bgUsage , bgPolicy.getPercentage(uid, bgUsage),
- exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
- reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
- mUidBatteryUsage.get(uid, 0.0d));
+ synchronized (mLock) {
+ final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+ pw.print(" " + prefix);
+ pw.print("Boot=");
+ TimeUtils.dumpTime(pw, mBootTimestamp);
+ pw.print(" Last battery usage start=");
+ TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
+ pw.println();
+ pw.print(" " + prefix);
+ pw.print("Battery usage over last ");
+ final String newPrefix = " " + prefix;
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ final long now = SystemClock.elapsedRealtime();
+ final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
+ pw.println(TimeUtils.formatDuration(now - since));
+ if (uidConsumers.size() == 0) {
+ pw.print(newPrefix);
+ pw.println("(none)");
+ } else {
+ for (int i = 0, size = uidConsumers.size(); i < size; i++) {
+ final int uid = uidConsumers.keyAt(i);
+ final double bgUsage = uidConsumers.valueAt(i);
+ final double exemptedUsage = mAppRestrictionController
+ .getUidBatteryExemptedUsageSince(uid, since, now);
+ final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
+ pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
+ + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
+ newPrefix, UserHandle.formatUid(uid),
+ PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
+ bgUsage , bgPolicy.getPercentage(uid, bgUsage),
+ exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
+ reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
+ mUidBatteryUsage.get(uid, 0.0d));
+ }
}
}
super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c8ad0e8..a23de22 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1365,7 +1365,7 @@
}
public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
- final int serviceType) {
+ final int serviceType, final int nrFrequency) {
enforceCallingPermission();
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1373,7 +1373,7 @@
mHandler.post(() -> {
synchronized (mStats) {
mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
- elapsedRealtime, uptime);
+ nrFrequency, elapsedRealtime, uptime);
}
});
}
diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java
index 6e39a4c..f0910dc 100644
--- a/services/core/java/com/android/server/am/DataConnectionStats.java
+++ b/services/core/java/com/android/server/am/DataConnectionStats.java
@@ -109,7 +109,7 @@
}
try {
mBatteryStats.notePhoneDataConnectionState(networkType, visible,
- mServiceState.getState());
+ mServiceState.getState(), mServiceState.getNrFrequencyRange());
} catch (RemoteException e) {
Log.w(TAG, "Error noting data connection state", e);
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 42fca9b..47f31d5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -486,9 +486,7 @@
return;
}
final BluetoothDevice btDevice = deviceList.get(0);
- final @BluetoothProfile.BtProfileState int state =
- proxy.getConnectionState(btDevice);
- if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) {
mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(btDevice, null,
new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java
new file mode 100644
index 0000000..84fa0cc
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.display;
+
+import android.util.MathUtils;
+
+/**
+ * Utility class providing functions to convert between linear and perceptual gamma space.
+ *
+ * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is a
+ * slight improvement to the typical gamma transfer function for displays whose max brightness
+ * exceeds the 120 nit reference point, but doesn't set a specific reference brightness like the PQ
+ * function does.
+ *
+ * Note that this transfer function is only valid if the display's backlight value is a linear
+ * control. If it's calibrated to be something non-linear, then a different transfer function
+ * should be used.
+ *
+ * Note: This code is based on the same class in the com.android.settingslib.display package.
+ */
+public class BrightnessUtils {
+
+ // Hybrid Log Gamma constant values
+ private static final float R = 0.5f;
+ private static final float A = 0.17883277f;
+ private static final float B = 0.28466892f;
+ private static final float C = 0.55991073f;
+
+ /**
+ * A function for converting from the gamma space into the linear space.
+ *
+ * @param val The value in the gamma space [0 .. 1.0]
+ * @return The corresponding value in the linear space [0 .. 1.0].
+ */
+ public static final float convertGammaToLinear(float val) {
+ final float ret;
+ if (val <= R) {
+ ret = MathUtils.sq(val / R);
+ } else {
+ ret = MathUtils.exp((val - C) / A) + B;
+ }
+
+ // HLG is normalized to the range [0, 12], ensure that value is within that range,
+ // it shouldn't be out of bounds.
+ final float normalizedRet = MathUtils.constrain(ret, 0, 12);
+
+ // Re-normalize to the range [0, 1]
+ // in order to derive the correct setting value.
+ return normalizedRet / 12;
+ }
+
+ /**
+ * A function for converting from the linear space into the gamma space.
+ *
+ * @param val The value in linear space [0 .. 1.0]
+ * @return The corresponding value in gamma space [0 .. 1.0]
+ */
+ public static final float convertLinearToGamma(float val) {
+ // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1]
+ final float normalizedVal = val * 12;
+ final float ret;
+ if (normalizedVal <= 1f) {
+ ret = MathUtils.sqrt(normalizedVal) * R;
+ } else {
+ ret = A * MathUtils.log(normalizedVal - B) + C;
+ }
+ return ret;
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7ad4979..4e88acd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -141,7 +141,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -1756,10 +1755,6 @@
void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) {
synchronized (mSyncRoot) {
- if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) {
- return;
- }
-
if (mode != null && !isResolutionAndRefreshRateValid(mode)
&& displayId == Display.INVALID_DISPLAY) {
throw new IllegalArgumentException("width, height and refresh rate of mode should "
@@ -1813,7 +1808,15 @@
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
- device.setUserPreferredDisplayModeLocked(mode);
+ // If there is a display specific mode, don't override that
+ final Point deviceUserPreferredResolution =
+ mPersistentDataStore.getUserPreferredResolution(device);
+ final float deviceRefreshRate =
+ mPersistentDataStore.getUserPreferredRefreshRate(device);
+ if (!isValidResolution(deviceUserPreferredResolution)
+ && !isValidRefreshRate(deviceRefreshRate)) {
+ device.setUserPreferredDisplayModeLocked(mode);
+ }
});
}
@@ -3533,6 +3536,14 @@
&& (brightness <= PowerManager.BRIGHTNESS_MAX);
}
+ private static boolean isValidResolution(Point resolution) {
+ return (resolution != null) && (resolution.x > 0) && (resolution.y > 0);
+ }
+
+ private static boolean isValidRefreshRate(float refreshRate) {
+ return !Float.isNaN(refreshRate) && (refreshRate > 0.0f);
+ }
+
private final class LocalService extends DisplayManagerInternal {
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index a9a1f08..a9875c8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -335,7 +335,7 @@
}
private int setUserDisabledHdrTypes() {
- final String[] userDisabledHdrTypesText = getAllArgs();
+ String[] userDisabledHdrTypesText = peekRemainingArgs();
if (userDisabledHdrTypesText == null) {
getErrPrintWriter().println("Error: no userDisabledHdrTypes specified");
return 1;
@@ -351,7 +351,6 @@
getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes");
return 1;
}
-
final Context context = mService.getContext();
final DisplayManager dm = context.getSystemService(DisplayManager.class);
dm.setUserDisabledHdrTypes(userDisabledHdrTypes);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index ed3b15f..1ebd1f5a 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -23,6 +23,8 @@
/**
* A custom animator that progressively updates a property value at
* a given variable rate until it reaches a particular target value.
+ * The ramping at the given rate is done in the perceptual space using
+ * the HLG transfer functions.
*/
class RampAnimator<T> {
private final T mObject;
@@ -57,7 +59,9 @@
* @param rate The convergence rate in units per second, or 0 to set the value immediately.
* @return True if the target differs from the previous target.
*/
- public boolean animateTo(float target, float rate) {
+ public boolean animateTo(float targetLinear, float rate) {
+ // Convert the target from the linear into the HLG space.
+ final float target = BrightnessUtils.convertLinearToGamma(targetLinear);
// Immediately jump to the target the first time.
if (mFirstTime || rate <= 0) {
@@ -156,7 +160,9 @@
final float oldCurrentValue = mCurrentValue;
mCurrentValue = mAnimatedValue;
if (oldCurrentValue != mCurrentValue) {
- mProperty.setValue(mObject, mCurrentValue);
+ // Convert value from HLG into linear space for the property.
+ final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue);
+ mProperty.setValue(mObject, linearCurrentVal);
}
if (mTargetValue != mCurrentValue) {
postAnimationCallback();
@@ -201,14 +207,14 @@
* If this is the first time the property is being set or if the rate is 0,
* the value jumps directly to the target.
*
- * @param firstTarget The first target value.
- * @param secondTarget The second target value.
+ * @param linearFirstTarget The first target value in linear space.
+ * @param linearSecondTarget The second target value in linear space.
* @param rate The convergence rate in units per second, or 0 to set the value immediately.
* @return True if either target differs from the previous target.
*/
- public boolean animateTo(float firstTarget, float secondTarget, float rate) {
- final boolean firstRetval = mFirst.animateTo(firstTarget, rate);
- final boolean secondRetval = mSecond.animateTo(secondTarget, rate);
+ public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate) {
+ final boolean firstRetval = mFirst.animateTo(linearFirstTarget, rate);
+ final boolean secondRetval = mSecond.animateTo(linearSecondTarget, rate);
return firstRetval && secondRetval;
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index de933cc..783a88c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2285,14 +2285,8 @@
nativeNotifyPortAssociationsChanged(mPtr);
}
- /**
- * Add a runtime association between the input device name and the display unique id.
- * @param inputDeviceName The name of the input device.
- * @param displayUniqueId The unique id of the associated display.
- */
@Override // Binder call
- public void addUniqueIdAssociation(@NonNull String inputDeviceName,
- @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"addNameAssociation()")) {
@@ -2300,20 +2294,16 @@
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
- Objects.requireNonNull(inputDeviceName);
+ Objects.requireNonNull(inputPort);
Objects.requireNonNull(displayUniqueId);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.put(inputDeviceName, displayUniqueId);
+ mUniqueIdAssociations.put(inputPort, displayUniqueId);
}
nativeChangeUniqueIdAssociation(mPtr);
}
- /**
- * Remove the runtime association between the input device and the display.
- * @param inputDeviceName The port of the input device to be cleared.
- */
@Override // Binder call
- public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+ public void removeUniqueIdAssociation(@NonNull String inputPort) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"removeUniqueIdAssociation()")) {
@@ -2321,9 +2311,9 @@
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
- Objects.requireNonNull(inputDeviceName);
+ Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputDeviceName);
+ mUniqueIdAssociations.remove(inputPort);
}
nativeChangeUniqueIdAssociation(mPtr);
}
@@ -2594,6 +2584,13 @@
pw.println(" display: " + v);
});
}
+ if (!mUniqueIdAssociations.isEmpty()) {
+ pw.println("Unique Id Associations:");
+ mUniqueIdAssociations.forEach((k, v) -> {
+ pw.print(" port: " + k);
+ pw.println(" uniqueId: " + v);
+ });
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 583cdd5..647a89e 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -104,6 +104,12 @@
return -1 * Boolean.compare(leftPeople, rightPeople);
}
+ boolean leftSystemMax = isSystemMax(left);
+ boolean rightSystemMax = isSystemMax(right);
+ if (leftSystemMax != rightSystemMax) {
+ return -1 * Boolean.compare(leftSystemMax, rightSystemMax);
+ }
+
if (leftImportance != rightImportance) {
// by importance, high to low
return -1 * Integer.compare(leftImportance, rightImportance);
@@ -173,6 +179,20 @@
return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance());
}
+ protected boolean isSystemMax(NotificationRecord record) {
+ if (record.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+ return false;
+ }
+ String packageName = record.getSbn().getPackageName();
+ if ("android".equals(packageName)) {
+ return true;
+ }
+ if ("com.android.systemui".equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
private boolean isOngoing(NotificationRecord record) {
final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
return (record.getNotification().flags & ongoingFlags) != 0;
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 1fa9013..9bcb724 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -285,6 +285,19 @@
);
/**
+ * User restrictions available to a device owner whose type is
+ * {@link android.app.admin.DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+ */
+ private static final Set<String> FINANCED_DEVICE_OWNER_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
+ UserManager.DISALLOW_OUTGOING_CALLS
+ );
+
+ /**
* Returns whether the given restriction name is valid (and logs it if it isn't).
*/
public static boolean isValidRestriction(@NonNull String restriction) {
@@ -458,6 +471,15 @@
}
/**
+ * @return {@code true} only if the restriction is allowed for financed devices and can be set
+ * by a device owner. Otherwise, {@code false} would be returned.
+ */
+ public static boolean canFinancedDeviceOwnerChange(String restriction) {
+ return FINANCED_DEVICE_OWNER_RESTRICTIONS.contains(restriction)
+ && canDeviceOwnerChange(restriction);
+ }
+
+ /**
* Whether given user restriction should be enforced globally.
*/
public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType,
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
new file mode 100644
index 0000000..cea84b5
--- /dev/null
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2021 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.power;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Controls Low Power Standby state.
+ *
+ * Instantiated by {@link PowerManagerService} only if Low Power Standby is supported.
+ *
+ * <p>Low Power Standby is active when all of the following conditions are met:
+ * <ul>
+ * <li>Low Power Standby is enabled
+ * <li>The device is not interactive, and has been non-interactive for a given timeout
+ * <li>The device is not in a doze maintenance window
+ * </ul>
+ *
+ * <p>When Low Power Standby is active, the following restrictions are applied to applications
+ * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP}:
+ * <ul>
+ * <li>Network access is blocked
+ * <li>Wakelocks are disabled
+ * </ul>
+ *
+ * @hide
+ */
+public final class LowPowerStandbyController {
+ private static final String TAG = "LowPowerStandbyController";
+ private static final boolean DEBUG = false;
+ private static final boolean DEFAULT_ACTIVE_DURING_MAINTENANCE = false;
+
+ private static final int MSG_STANDBY_TIMEOUT = 0;
+ private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1;
+ private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2;
+
+ private final Handler mHandler;
+ private final SettingsObserver mSettingsObserver;
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final Clock mClock;
+ private final AlarmManager.OnAlarmListener mOnStandbyTimeoutExpired =
+ this::onStandbyTimeoutExpired;
+ private final LowPowerStandbyControllerInternal mLocalService = new LocalService();
+ private final SparseBooleanArray mAllowlistUids = new SparseBooleanArray();
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_SCREEN_OFF:
+ onNonInteractive();
+ break;
+ case Intent.ACTION_SCREEN_ON:
+ onInteractive();
+ break;
+ case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+ onDeviceIdleModeChanged();
+ break;
+ }
+ }
+ };
+
+ @GuardedBy("mLock")
+ private AlarmManager mAlarmManager;
+ @GuardedBy("mLock")
+ private PowerManager mPowerManager;
+ @GuardedBy("mLock")
+ private boolean mSupportedConfig;
+ @GuardedBy("mLock")
+ private boolean mEnabledByDefaultConfig;
+ @GuardedBy("mLock")
+ private int mStandbyTimeoutConfig;
+
+ /** Whether Low Power Standby is enabled in Settings */
+ @GuardedBy("mLock")
+ private boolean mIsEnabled;
+
+ /**
+ * Whether Low Power Standby is currently active (enforcing restrictions).
+ */
+ @GuardedBy("mLock")
+ private boolean mIsActive;
+
+ /** Whether the device is currently interactive */
+ @GuardedBy("mLock")
+ private boolean mIsInteractive;
+
+ /** The time the device was last interactive, in {@link SystemClock#elapsedRealtime()}. */
+ @GuardedBy("mLock")
+ private long mLastInteractiveTimeElapsed;
+
+ /**
+ * Whether we are in device idle mode.
+ * During maintenance windows Low Power Standby is deactivated to allow
+ * apps to run maintenance tasks.
+ */
+ @GuardedBy("mLock")
+ private boolean mIsDeviceIdle;
+
+ /**
+ * Whether the device has entered idle mode since becoming non-interactive.
+ * In the initial non-idle period after turning the screen off, Low Power Standby is already
+ * allowed to become active. Later non-idle periods are treated as maintenance windows, during
+ * which Low Power Standby is deactivated to allow apps to run maintenance tasks.
+ */
+ @GuardedBy("mLock")
+ private boolean mIdleSinceNonInteractive;
+
+ /** Whether Low Power Standby restrictions should be active during doze maintenance mode. */
+ @GuardedBy("mLock")
+ private boolean mActiveDuringMaintenance;
+
+ /** Force Low Power Standby to be active. */
+ @GuardedBy("mLock")
+ private boolean mForceActive;
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /** Returns milliseconds since boot, including time spent in sleep. */
+ long elapsedRealtime();
+ }
+
+ public LowPowerStandbyController(Context context, Looper looper, Clock clock) {
+ mContext = context;
+ mHandler = new LowPowerStandbyHandler(looper);
+ mClock = clock;
+ mSettingsObserver = new SettingsObserver(mHandler);
+ }
+
+ void systemReady() {
+ final Resources resources = mContext.getResources();
+ synchronized (mLock) {
+ mSupportedConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbySupported);
+
+ if (!mSupportedConfig) {
+ return;
+ }
+
+ mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+
+ mStandbyTimeoutConfig = resources.getInteger(
+ R.integer.config_lowPowerStandbyNonInteractiveTimeout);
+ mEnabledByDefaultConfig = resources.getBoolean(
+ R.bool.config_lowPowerStandbyEnabledByDefault);
+
+ mIsInteractive = mPowerManager.isInteractive();
+
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_STANDBY_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ updateSettingsLocked();
+
+ if (mIsEnabled) {
+ registerBroadcastReceiver();
+ }
+ }
+
+ LocalServices.addService(LowPowerStandbyControllerInternal.class, mLocalService);
+ }
+
+ @GuardedBy("mLock")
+ private void updateSettingsLocked() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ mIsEnabled = mSupportedConfig && Settings.Global.getInt(resolver,
+ Settings.Global.LOW_POWER_STANDBY_ENABLED,
+ mEnabledByDefaultConfig ? 1 : 0) != 0;
+ mActiveDuringMaintenance = Settings.Global.getInt(resolver,
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+ DEFAULT_ACTIVE_DURING_MAINTENANCE ? 1 : 0) != 0;
+
+ updateActiveLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateActiveLocked() {
+ final long now = mClock.elapsedRealtime();
+ final boolean standbyTimeoutExpired =
+ (now - mLastInteractiveTimeElapsed) >= mStandbyTimeoutConfig;
+ final boolean maintenanceMode = mIdleSinceNonInteractive && !mIsDeviceIdle;
+ final boolean newActive =
+ mForceActive || (mIsEnabled && !mIsInteractive && standbyTimeoutExpired
+ && (!maintenanceMode || mActiveDuringMaintenance));
+ if (DEBUG) {
+ Slog.d(TAG, "updateActiveLocked: mIsEnabled=" + mIsEnabled + ", mIsInteractive="
+ + mIsInteractive + ", standbyTimeoutExpired=" + standbyTimeoutExpired
+ + ", mIdleSinceNonInteractive=" + mIdleSinceNonInteractive + ", mIsDeviceIdle="
+ + mIsDeviceIdle + ", mActiveDuringMaintenance=" + mActiveDuringMaintenance
+ + ", mForceActive=" + mForceActive + ", mIsActive=" + mIsActive + ", newActive="
+ + newActive);
+ }
+ if (mIsActive != newActive) {
+ mIsActive = newActive;
+ if (DEBUG) {
+ Slog.d(TAG, "mIsActive changed, mIsActive=" + mIsActive);
+ }
+ enqueueNotifyActiveChangedLocked();
+ }
+ }
+
+ private void onNonInteractive() {
+ if (DEBUG) {
+ Slog.d(TAG, "onNonInteractive");
+ }
+ final long now = mClock.elapsedRealtime();
+ synchronized (mLock) {
+ mIsInteractive = false;
+ mIsDeviceIdle = false;
+ mLastInteractiveTimeElapsed = now;
+
+ if (mStandbyTimeoutConfig > 0) {
+ scheduleStandbyTimeoutAlarmLocked();
+ }
+
+ updateActiveLocked();
+ }
+ }
+
+ private void onInteractive() {
+ if (DEBUG) {
+ Slog.d(TAG, "onInteractive");
+ }
+
+ synchronized (mLock) {
+ cancelStandbyTimeoutAlarmLocked();
+ mIsInteractive = true;
+ mIsDeviceIdle = false;
+ mIdleSinceNonInteractive = false;
+ updateActiveLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleStandbyTimeoutAlarmLocked() {
+ final long nextAlarmTime = SystemClock.elapsedRealtime() + mStandbyTimeoutConfig;
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ nextAlarmTime, "LowPowerStandbyController.StandbyTimeout",
+ mOnStandbyTimeoutExpired, mHandler);
+ }
+
+ @GuardedBy("mLock")
+ private void cancelStandbyTimeoutAlarmLocked() {
+ mAlarmManager.cancel(mOnStandbyTimeoutExpired);
+ }
+
+ private void onDeviceIdleModeChanged() {
+ synchronized (mLock) {
+ mIsDeviceIdle = mPowerManager.isDeviceIdleMode();
+ if (DEBUG) {
+ Slog.d(TAG, "onDeviceIdleModeChanged, mIsDeviceIdle=" + mIsDeviceIdle);
+ }
+
+ mIdleSinceNonInteractive = mIdleSinceNonInteractive || mIsDeviceIdle;
+ updateActiveLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onEnabledLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "onEnabledLocked");
+ }
+
+ if (mPowerManager.isInteractive()) {
+ onInteractive();
+ } else {
+ onNonInteractive();
+ }
+
+ registerBroadcastReceiver();
+ }
+
+ @GuardedBy("mLock")
+ private void onDisabledLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "onDisabledLocked");
+ }
+
+ cancelStandbyTimeoutAlarmLocked();
+ unregisterBroadcastReceiver();
+ updateActiveLocked();
+ }
+
+ @VisibleForTesting
+ void onSettingsChanged() {
+ if (DEBUG) {
+ Slog.d(TAG, "onSettingsChanged");
+ }
+ synchronized (mLock) {
+ final boolean oldEnabled = mIsEnabled;
+ updateSettingsLocked();
+
+ if (mIsEnabled != oldEnabled) {
+ if (mIsEnabled) {
+ onEnabledLocked();
+ } else {
+ onDisabledLocked();
+ }
+
+ notifyEnabledChangedLocked();
+ }
+ }
+ }
+
+ private void registerBroadcastReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ @GuardedBy("mLock")
+ private void notifyEnabledChangedLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled);
+ }
+
+ final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void onStandbyTimeoutExpired() {
+ if (DEBUG) {
+ Slog.d(TAG, "onStandbyTimeoutExpired");
+ }
+ synchronized (mLock) {
+ updateActiveLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void enqueueNotifyActiveChangedLocked() {
+ final long now = mClock.elapsedRealtime();
+ final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive);
+ mHandler.sendMessageAtTime(msg, now);
+ }
+
+ /** Notify other system components about the updated Low Power Standby active state */
+ private void notifyActiveChanged(boolean active) {
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ pmi.setLowPowerStandbyActive(active);
+ }
+
+ @VisibleForTesting
+ boolean isActive() {
+ synchronized (mLock) {
+ return mIsActive;
+ }
+ }
+
+ boolean isSupported() {
+ synchronized (mLock) {
+ return mSupportedConfig;
+ }
+ }
+
+ boolean isEnabled() {
+ synchronized (mLock) {
+ return mSupportedConfig && mIsEnabled;
+ }
+ }
+
+ void setEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (!mSupportedConfig) {
+ Slog.w(TAG, "Low Power Standby cannot be enabled "
+ + "because it is not supported on this device");
+ return;
+ }
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.LOW_POWER_STANDBY_ENABLED, enabled ? 1 : 0);
+ onSettingsChanged();
+ }
+ }
+
+ void setActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ synchronized (mLock) {
+ if (!mSupportedConfig) {
+ Slog.w(TAG, "Low Power Standby settings cannot be changed "
+ + "because it is not supported on this device");
+ return;
+ }
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+ activeDuringMaintenance ? 1 : 0);
+ onSettingsChanged();
+ }
+ }
+
+ void forceActive(boolean active) {
+ synchronized (mLock) {
+ mForceActive = active;
+ updateActiveLocked();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+
+ ipw.println();
+ ipw.println("Low Power Standby Controller:");
+ ipw.increaseIndent();
+ synchronized (mLock) {
+ ipw.print("mIsActive=");
+ ipw.println(mIsActive);
+ ipw.print("mIsEnabled=");
+ ipw.println(mIsEnabled);
+ ipw.print("mSupportedConfig=");
+ ipw.println(mSupportedConfig);
+ ipw.print("mEnabledByDefaultConfig=");
+ ipw.println(mEnabledByDefaultConfig);
+ ipw.print("mStandbyTimeoutConfig=");
+ ipw.println(mStandbyTimeoutConfig);
+
+ if (mIsActive || mIsEnabled) {
+ ipw.print("mIsInteractive=");
+ ipw.println(mIsInteractive);
+ ipw.print("mLastInteractiveTime=");
+ ipw.println(mLastInteractiveTimeElapsed);
+ ipw.print("mIdleSinceNonInteractive=");
+ ipw.println(mIdleSinceNonInteractive);
+ ipw.print("mIsDeviceIdle=");
+ ipw.println(mIsDeviceIdle);
+ }
+
+ final int[] allowlistUids = getAllowlistUidsLocked();
+ ipw.print("mAllowlistUids=");
+ ipw.println(Arrays.toString(allowlistUids));
+ }
+ ipw.decreaseIndent();
+ }
+
+ void dumpProto(ProtoOutputStream proto, long tag) {
+ synchronized (mLock) {
+ final long token = proto.start(tag);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_ACTIVE, mIsActive);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED, mIsEnabled);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_SUPPORTED_CONFIG, mSupportedConfig);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED_BY_DEFAULT_CONFIG,
+ mEnabledByDefaultConfig);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_INTERACTIVE, mIsInteractive);
+ proto.write(LowPowerStandbyControllerDumpProto.LAST_INTERACTIVE_TIME,
+ mLastInteractiveTimeElapsed);
+ proto.write(LowPowerStandbyControllerDumpProto.STANDBY_TIMEOUT_CONFIG,
+ mStandbyTimeoutConfig);
+ proto.write(LowPowerStandbyControllerDumpProto.IDLE_SINCE_NON_INTERACTIVE,
+ mIdleSinceNonInteractive);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_DEVICE_IDLE, mIsDeviceIdle);
+
+ final int[] allowlistUids = getAllowlistUidsLocked();
+ for (int appId : allowlistUids) {
+ proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId);
+ }
+
+ proto.end(token);
+ }
+ }
+
+ private class LowPowerStandbyHandler extends Handler {
+ LowPowerStandbyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_STANDBY_TIMEOUT:
+ onStandbyTimeoutExpired();
+ break;
+ case MSG_NOTIFY_ACTIVE_CHANGED:
+ boolean active = (boolean) msg.obj;
+ notifyActiveChanged(active);
+ break;
+ case MSG_NOTIFY_ALLOWLIST_CHANGED:
+ final int[] allowlistUids = (int[]) msg.obj;
+ notifyAllowlistChanged(allowlistUids);
+ break;
+ }
+ }
+ }
+
+ private void addToAllowlistInternal(int uid) {
+ if (DEBUG) {
+ Slog.i(TAG, "Adding to allowlist: " + uid);
+ }
+ synchronized (mLock) {
+ if (mSupportedConfig && !mAllowlistUids.get(uid)) {
+ mAllowlistUids.append(uid, true);
+ enqueueNotifyAllowlistChangedLocked();
+ }
+ }
+ }
+
+ private void removeFromAllowlistInternal(int uid) {
+ if (DEBUG) {
+ Slog.i(TAG, "Removing from allowlist: " + uid);
+ }
+ synchronized (mLock) {
+ if (mSupportedConfig && mAllowlistUids.get(uid)) {
+ mAllowlistUids.delete(uid);
+ enqueueNotifyAllowlistChangedLocked();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int[] getAllowlistUidsLocked() {
+ final int[] uids = new int[mAllowlistUids.size()];
+ for (int i = 0; i < mAllowlistUids.size(); i++) {
+ uids[i] = mAllowlistUids.keyAt(i);
+ }
+ return uids;
+ }
+
+ @GuardedBy("mLock")
+ private void enqueueNotifyAllowlistChangedLocked() {
+ final long now = mClock.elapsedRealtime();
+ final int[] allowlistUids = getAllowlistUidsLocked();
+ final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ALLOWLIST_CHANGED, allowlistUids);
+ mHandler.sendMessageAtTime(msg, now);
+ }
+
+ private void notifyAllowlistChanged(int[] allowlistUids) {
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ pmi.setLowPowerStandbyAllowlist(allowlistUids);
+ }
+
+ private final class LocalService extends LowPowerStandbyControllerInternal {
+ @Override
+ public void addToAllowlist(int uid) {
+ addToAllowlistInternal(uid);
+ }
+
+ @Override
+ public void removeFromAllowlist(int uid) {
+ removeFromAllowlistInternal(uid);
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ onSettingsChanged();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java
new file mode 100644
index 0000000..f6953fa
--- /dev/null
+++ b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.power;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class LowPowerStandbyControllerInternal {
+ /**
+ * Adds an application to the Low Power Standby allowlist,
+ * exempting it from Low Power Standby restrictions.
+ *
+ * @param uid UID to add to allowlist.
+ */
+ public abstract void addToAllowlist(int uid);
+
+ /**
+ * Removes an application from the Low Power Standby allowlist.
+ *
+ * @param uid UID to remove from allowlist.
+ */
+ public abstract void removeFromAllowlist(int uid);
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4185b2d9..efcfbdd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -37,6 +37,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
@@ -276,6 +277,7 @@
private final BatterySaverPolicy mBatterySaverPolicy;
private final BatterySaverStateMachine mBatterySaverStateMachine;
private final BatterySavingStats mBatterySavingStats;
+ private final LowPowerStandbyController mLowPowerStandbyController;
private final AttentionDetector mAttentionDetector;
private final FaceDownDetector mFaceDownDetector;
private final ScreenUndimDetector mScreenUndimDetector;
@@ -609,12 +611,17 @@
// True if we are currently in light device idle mode.
private boolean mLightDeviceIdleMode;
- // Set of app ids that we will always respect the wake locks for.
+ // Set of app ids that we will respect the wake locks for while in device idle mode.
int[] mDeviceIdleWhitelist = new int[0];
// Set of app ids that are temporarily allowed to acquire wakelocks due to high-pri message
int[] mDeviceIdleTempWhitelist = new int[0];
+ // Set of app ids that are allowed to acquire wakelocks while low power standby is active
+ int[] mLowPowerStandbyAllowlist = new int[0];
+
+ private boolean mLowPowerStandbyActive;
+
private final SparseArray<UidState> mUidState = new SparseArray<>();
// A mapping from DisplayGroup Id to PowerGroup. There is a 1-1 mapping between DisplayGroups
@@ -966,6 +973,10 @@
void invalidateIsInteractiveCaches() {
PowerManager.invalidateIsInteractiveCaches();
}
+
+ LowPowerStandbyController createLowPowerStandbyController(Context context, Looper looper) {
+ return new LowPowerStandbyController(context, looper, SystemClock::elapsedRealtime);
+ }
}
final Constants mConstants;
@@ -1015,6 +1026,8 @@
mBatterySaverStateMachine = mInjector.createBatterySaverStateMachine(mLock, mContext,
mBatterySaverController);
+ mLowPowerStandbyController = mInjector.createLowPowerStandbyController(mContext,
+ Looper.getMainLooper());
mInattentiveSleepWarningOverlayController =
mInjector.createInattentiveSleepWarningController();
@@ -1228,6 +1241,8 @@
// Shouldn't happen since in-process.
}
+ mLowPowerStandbyController.systemReady();
+
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -1654,6 +1669,16 @@
}
@GuardedBy("mLock")
+ @VisibleForTesting
+ WakeLock findWakeLockLocked(IBinder lock) {
+ int index = findWakeLockIndexLocked(lock);
+ if (index == -1) {
+ return null;
+ }
+ return mWakeLocks.get(index);
+ }
+
+ @GuardedBy("mLock")
private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) {
if (mSystemReady && !wakeLock.mDisabled) {
wakeLock.mNotifiedAcquired = true;
@@ -3852,6 +3877,24 @@
}
}
+ void setLowPowerStandbyAllowlistInternal(int[] appids) {
+ synchronized (mLock) {
+ mLowPowerStandbyAllowlist = appids;
+ if (mLowPowerStandbyActive) {
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
+ void setLowPowerStandbyActiveInternal(boolean active) {
+ synchronized (mLock) {
+ if (mLowPowerStandbyActive != active) {
+ mLowPowerStandbyActive = active;
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
void startUidChangesInternal() {
synchronized (mLock) {
mUidsChanging = true;
@@ -3888,7 +3931,7 @@
<= ActivityManager.PROCESS_STATE_RECEIVER;
state.mProcState = procState;
if (state.mNumWakeLocks > 0) {
- if (mDeviceIdleMode) {
+ if (mDeviceIdleMode || mLowPowerStandbyActive) {
handleUidStateChangeLocked();
} else if (!state.mActive && oldShouldAllow !=
(procState <= ActivityManager.PROCESS_STATE_RECEIVER)) {
@@ -3908,7 +3951,7 @@
state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
state.mActive = false;
mUidState.removeAt(index);
- if (mDeviceIdleMode && state.mNumWakeLocks > 0) {
+ if ((mDeviceIdleMode || mLowPowerStandbyActive) && state.mNumWakeLocks > 0) {
handleUidStateChangeLocked();
}
}
@@ -3993,6 +4036,14 @@
disabled = true;
}
}
+ if (mLowPowerStandbyActive) {
+ final UidState state = wakeLock.mUidState;
+ if (Arrays.binarySearch(mLowPowerStandbyAllowlist, appid) < 0
+ && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT
+ && state.mProcState > ActivityManager.PROCESS_STATE_BOUND_TOP) {
+ disabled = true;
+ }
+ }
}
if (wakeLock.mDisabled != disabled) {
wakeLock.mDisabled = disabled;
@@ -4260,6 +4311,7 @@
pw.println("POWER MANAGER (dumpsys power)\n");
final WirelessChargerDetector wcd;
+ final LowPowerStandbyController lowPowerStandbyController;
synchronized (mLock) {
pw.println("Power Manager State:");
mConstants.dump(pw);
@@ -4316,6 +4368,7 @@
pw.println(" mDeviceIdleMode=" + mDeviceIdleMode);
pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
pw.println(" mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
+ pw.println(" mLowPowerStandbyActive=" + mLowPowerStandbyActive);
pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastGlobalWakeTime));
pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastGlobalSleepTime));
pw.println(" mLastSleepReason=" + PowerManager.sleepReasonToString(
@@ -4491,10 +4544,13 @@
mFaceDownDetector.dump(pw);
mAmbientDisplaySuppressionController.dump(pw);
+
+ mLowPowerStandbyController.dump(pw);
}
private void dumpProto(FileDescriptor fd) {
final WirelessChargerDetector wcd;
+ final LowPowerStandbyController lowPowerStandbyController;
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mLock) {
@@ -4599,6 +4655,9 @@
proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
}
+ proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_STANDBY_ACTIVE,
+ mLowPowerStandbyActive);
+
proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastGlobalWakeTime);
proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastGlobalSleepTime);
proto.write(
@@ -4832,6 +4891,7 @@
for (SuspendBlocker sb : mSuspendBlockers) {
sb.dumpDebug(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS);
}
+
wcd = mWirelessChargerDetector;
}
@@ -4839,6 +4899,9 @@
wcd.dumpDebug(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
}
+ mLowPowerStandbyController.dumpProto(proto,
+ PowerManagerServiceDumpProto.LOW_POWER_STANDBY_CONTROLLER);
+
proto.flush();
}
@@ -5092,6 +5155,8 @@
(mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP)!=0);
proto.write(WakeLockProto.WakeLockFlagsProto.IS_ON_AFTER_RELEASE,
(mFlags & PowerManager.ON_AFTER_RELEASE)!=0);
+ proto.write(WakeLockProto.WakeLockFlagsProto.SYSTEM_WAKELOCK,
+ (mFlags & PowerManager.SYSTEM_WAKELOCK) != 0);
proto.end(wakeLockFlagsToken);
proto.write(WakeLockProto.IS_DISABLED, mDisabled);
@@ -5138,6 +5203,9 @@
if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) {
result += " ON_AFTER_RELEASE";
}
+ if ((mFlags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+ result += " SYSTEM_WAKELOCK";
+ }
return result;
}
}
@@ -5299,8 +5367,22 @@
ws = null;
}
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+
+ if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ WorkSource workSource = new WorkSource(Binder.getCallingUid(), packageName);
+ if (ws != null && !ws.isEmpty()) {
+ workSource.add(ws);
+ }
+ ws = workSource;
+
+ uid = Process.myUid();
+ pid = Process.myPid();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
@@ -5768,6 +5850,100 @@
}
}
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public boolean isLowPowerStandbySupported() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "isLowPowerStandbySupported");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mLowPowerStandbyController.isSupported();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean isLowPowerStandbyEnabled() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mLowPowerStandbyController.isEnabled();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyEnabled(boolean enabled) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "setLowPowerStandbyEnabled");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mLowPowerStandbyController.setEnabled(enabled);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "setLowPowerStandbyActiveDuringMaintenance");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mLowPowerStandbyController.setActiveDuringMaintenance(activeDuringMaintenance);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void forceLowPowerStandbyActive(boolean active) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "forceLowPowerStandbyActive");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mLowPowerStandbyController.forceActive(active);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Gets the reason for the last time the phone had to reboot.
*
@@ -6249,6 +6425,16 @@
}
@Override
+ public void setLowPowerStandbyAllowlist(int[] appids) {
+ setLowPowerStandbyAllowlistInternal(appids);
+ }
+
+ @Override
+ public void setLowPowerStandbyActive(boolean enabled) {
+ setLowPowerStandbyActiveInternal(enabled);
+ }
+
+ @Override
public void startUidChanges() {
startUidChangesInternal();
}
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index 88c9850..d20c7f1 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -106,6 +106,7 @@
*/
private static final int FLAG_ON_AFTER_RELEASE = 0x8;
private static final int FLAG_ACQUIRE_CAUSES_WAKEUP = 0x10;
+ private static final int FLAG_SYSTEM_WAKELOCK = 0x20;
private static final int MASK_LOWER_6_BITS = 0x3F;
private static final int MASK_LOWER_7_BITS = 0x7F;
@@ -296,6 +297,9 @@
if ((flags & PowerManager.ON_AFTER_RELEASE) != 0) {
newFlags |= FLAG_ON_AFTER_RELEASE;
}
+ if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+ newFlags |= FLAG_SYSTEM_WAKELOCK;
+ }
return newFlags;
}
@@ -455,6 +459,9 @@
if ((flags & FLAG_ACQUIRE_CAUSES_WAKEUP) == FLAG_ACQUIRE_CAUSES_WAKEUP) {
sb.append(",acq-causes-wake");
}
+ if ((flags & FLAG_SYSTEM_WAKELOCK) == FLAG_SYSTEM_WAKELOCK) {
+ sb.append(",system-wakelock");
+ }
}
}
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index ff2f08b..27c0bee 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -15,34 +15,56 @@
*/
package com.android.server.tracing;
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.os.IMessenger;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.UserHandle;
+import android.service.tracing.TraceReportService;
import android.tracing.ITracingServiceProxy;
+import android.tracing.TraceReportParams;
import android.util.Log;
+import android.util.LruCache;
+import android.util.Slog;
+import com.android.internal.infra.ServiceConnector;
import com.android.server.SystemService;
+import java.io.IOException;
+
/**
* TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the
- * system tracing app Traceur.
+ * other components (e.g. system tracing app Traceur, trace reporting apps).
*
* Access to this service is restricted via SELinux. Normal apps do not have access.
*
* @hide
*/
public class TracingServiceProxy extends SystemService {
- private static final String TAG = "TracingServiceProxy";
-
public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy";
-
+ private static final String TAG = "TracingServiceProxy";
private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur";
private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService";
+ private static final int MAX_CACHED_REPORTER_SERVICES = 8;
+
+ // The maximum size of the trace allowed if the option |usePipeForTesting| is set.
+ // Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can
+ // write to the pipe without blocking) to avoid system_server blocking on this.
+ // (on Linux, the minimum value is 4K i.e. 1 minimally sized page).
+ private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024;
+
// Keep this in sync with the definitions in TraceService
private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
"com.android.traceur.NOTIFY_SESSION_STOPPED";
@@ -51,16 +73,22 @@
private final Context mContext;
private final PackageManager mPackageManager;
+ private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
/**
- * Notifies system tracing app that a tracing session has ended. If a session is repurposed
- * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
- * there is no buffer available to dump.
- */
+ * Notifies system tracing app that a tracing session has ended. If a session is repurposed
+ * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
+ * there is no buffer available to dump.
+ */
@Override
public void notifyTraceSessionEnded(boolean sessionStolen) {
- notifyTraceur(sessionStolen);
+ TracingServiceProxy.this.notifyTraceur(sessionStolen);
+ }
+
+ @Override
+ public void reportTrace(@NonNull TraceReportParams params) {
+ TracingServiceProxy.this.reportTrace(params);
}
};
@@ -68,6 +96,7 @@
super(context);
mContext = context;
mPackageManager = context.getPackageManager();
+ mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES);
}
@Override
@@ -103,4 +132,119 @@
Log.e(TAG, "Failed to locate Traceur", e);
}
}
+
+ private void reportTrace(@NonNull TraceReportParams params) {
+ // We don't need to do any permission checks on the caller because access
+ // to this service is guarded by SELinux.
+ ComponentName component = new ComponentName(params.reporterPackageName,
+ params.reporterClassName);
+ if (!hasBindServicePermission(component)) {
+ return;
+ }
+ boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
+ boolean hasUsageStatsPermission = hasPermission(component,
+ Manifest.permission.PACKAGE_USAGE_STATS);
+ if (!hasDumpPermission || !hasUsageStatsPermission) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ reportTrace(getOrCreateReporterService(component), params);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void reportTrace(
+ @NonNull ServiceConnector<IMessenger> reporterService,
+ @NonNull TraceReportParams params) {
+ reporterService.post(messenger -> {
+ if (params.usePipeForTesting) {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) {
+ try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) {
+ byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE);
+ if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) {
+ throw new IllegalArgumentException(
+ "Trace file too large when |usePipeForTesting| is set.");
+ }
+ o.write(array);
+ }
+ }
+ params.fd = pipe[0];
+ }
+
+ Message message = Message.obtain();
+ message.what = TraceReportService.MSG_REPORT_TRACE;
+ message.obj = params;
+ messenger.send(message);
+ }).whenComplete((res, err) -> {
+ if (err != null) {
+ Slog.e(TAG, "Failed to report trace", err);
+ }
+ try {
+ params.fd.close();
+ } catch (IOException ignored) {
+ }
+ });
+ }
+
+ private ServiceConnector<IMessenger> getOrCreateReporterService(
+ @NonNull ComponentName component) {
+ ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component);
+ if (connector == null) {
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ connector = new ServiceConnector.Impl<IMessenger>(
+ mContext, intent,
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY,
+ mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) {
+ private static final long DISCONNECT_TIMEOUT_MS = 15_000;
+ private static final long REQUEST_TIMEOUT_MS = 10_000;
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return DISCONNECT_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return REQUEST_TIMEOUT_MS;
+ }
+ };
+ mCachedReporterServices.put(intent.getComponent(), connector);
+ }
+ return connector;
+ }
+
+ private boolean hasPermission(@NonNull ComponentName componentName,
+ @NonNull String permission) throws SecurityException {
+ if (mPackageManager.checkPermission(permission, componentName.getPackageName())
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.e(TAG,
+ "Trace reporting service " + componentName.toShortString() + " does not have "
+ + permission + " permission");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean hasBindServicePermission(@NonNull ComponentName componentName) {
+ ServiceInfo info;
+ try {
+ info = mPackageManager.getServiceInfo(componentName, 0);
+ } catch (NameNotFoundException ex) {
+ Slog.e(TAG,
+ "Trace reporting service " + componentName.toShortString() + " does not exist");
+ return false;
+ }
+ if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) {
+ Slog.e(TAG,
+ "Trace reporting service " + componentName.toShortString()
+ + " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE
+ + " permission; instead requests " + info.permission);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9bed24d..52f7d10 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -122,7 +122,8 @@
private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14;
private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15;
- public static final int MSG_USER_REQUESTED_UNLOCK = 16;
+ private static final int MSG_USER_REQUESTED_UNLOCK = 16;
+ private static final int MSG_ENABLE_TRUST_AGENT = 17;
private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except";
@@ -631,6 +632,24 @@
}
}
+
+ /**
+ * Uses {@link LockPatternUtils} to enable the setting for trust agent in the specified
+ * component name. This should only be used for testing.
+ */
+ private void enableTrustAgentForUserForTest(@NonNull ComponentName componentName, int userId) {
+ Log.i(TAG,
+ "Enabling trust agent " + componentName.flattenToString() + " for user " + userId);
+ List<ComponentName> agents =
+ new ArrayList<>(mLockPatternUtils.getEnabledTrustAgents(userId));
+ if (!agents.contains(componentName)) {
+ agents.add(componentName);
+ }
+ // Even if the agent was already there, we still call setEnabledTrustAgents to trigger a
+ // refresh of installed agents.
+ mLockPatternUtils.setEnabledTrustAgents(agents, userId);
+ }
+
boolean isDeviceLockedInner(int userId) {
synchronized (mDeviceLockedForUser) {
return mDeviceLockedForUser.get(userId, true);
@@ -929,6 +948,7 @@
continue;
}
allowedAgents.add(resolveInfo);
+ if (DEBUG) Slog.d(TAG, "Adding agent " + getComponentName(resolveInfo));
}
return allowedAgents;
}
@@ -1158,6 +1178,13 @@
}
@Override
+ public void enableTrustAgentForUserForTest(ComponentName componentName, int userId)
+ throws RemoteException {
+ enforceReportPermission();
+ mHandler.obtainMessage(MSG_ENABLE_TRUST_AGENT, userId, 0, componentName).sendToTarget();
+ }
+
+ @Override
public void reportKeyguardShowingChanged() throws RemoteException {
enforceReportPermission();
// coalesce refresh messages.
@@ -1433,6 +1460,9 @@
// This is also called when the security mode of a user changes.
refreshDeviceLockedForUser(UserHandle.USER_ALL);
break;
+ case MSG_ENABLE_TRUST_AGENT:
+ enableTrustAgentForUserForTest((ComponentName) msg.obj, msg.arg1);
+ break;
case MSG_KEYGUARD_SHOWING_CHANGED:
refreshDeviceLockedForUser(mCurrentUser);
break;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7164c6c..ff96aeb 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -43,6 +43,7 @@
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -1081,7 +1082,7 @@
@Override
public void overridePendingTransition(IBinder token, String packageName,
- int enterAnim, int exitAnim) {
+ int enterAnim, int exitAnim, @ColorInt int backgroundColor) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
@@ -1091,7 +1092,7 @@
r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
- enterAnim, exitAnim, r.mOverrideTaskTransition),
+ enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
null /* startCallback */, null /* finishCallback */);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c0eee61..3908874 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4469,6 +4469,7 @@
pendingOptions.getOverrideTaskTransition());
options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
+ pendingOptions.getCustomBackgroundColor(),
pendingOptions.getOverrideTaskTransition());
startCallback = pendingOptions.getAnimationStartedListener();
finishCallback = pendingOptions.getAnimationFinishedListener();
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index a049d65..d4a7a5d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -380,8 +380,11 @@
final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
if (cn != null) {
try {
- final ApplicationInfo appInfo = AppGlobals.getPackageManager()
- .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+ final ApplicationInfo appInfo = AppGlobals.getPackageManager().getApplicationInfo(
+ cn.getPackageName(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS,
+ mService.mContext.getUserId());
if (appInfo != null) {
mRecentsUid = appInfo.uid;
mRecentsComponent = cn;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index ded58f4..ad45948 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1039,6 +1039,8 @@
" Rejecting as detached: %s", wc);
continue;
}
+ // The level of transition target should be at least window token.
+ if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 43018a9..adc91fc 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -186,7 +186,7 @@
};
/** Creates a new uinput device and assigns a file descriptor. */
-static int openUinput(const char* readableName, jint vendorId, jint productId,
+static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
DeviceType deviceType, jint screenHeight, jint screenWidth) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
if (fd < 0) {
@@ -194,6 +194,8 @@
return -errno;
}
+ ioctl(fd, UI_SET_PHYS, phys);
+
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_SYN);
switch (deviceType) {
@@ -295,28 +297,30 @@
return fd.release();
}
-static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys,
DeviceType deviceType, int screenHeight, int screenWidth) {
ScopedUtfChars readableName(env, name);
- return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
- screenWidth);
+ ScopedUtfChars readablePhys(env, phys);
+ return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
+ screenHeight, screenWidth);
}
static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
- /* screenWidth */ 0);
+ jint productId, jstring phys) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
+ /* screenHeight */ 0, /* screenWidth */ 0);
}
static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
- /* screenWidth */ 0);
+ jint productId, jstring phys) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
+ /* screenHeight */ 0, /* screenWidth */ 0);
}
static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId, jint height, jint width) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+ jint productId, jstring phys, jint height, jint width) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
+ width);
}
static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
@@ -435,9 +439,11 @@
}
static JNINativeMethod methods[] = {
- {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
- {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
- {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+ {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
+ (void*)nativeOpenUinputKeyboard},
+ {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
+ (void*)nativeOpenUinputMouse},
+ {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
(void*)nativeOpenUinputTouchscreen},
{"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
{"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0da8f7e..161d7ce 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1504,11 +1504,14 @@
JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval,
jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) {
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
- auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode),
- static_cast<IGnssAidl::GnssPositionRecurrence>(
- recurrence),
- min_interval, preferred_accuracy, preferred_time,
- low_power_mode);
+ IGnssAidl::PositionModeOptions options;
+ options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
+ options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
+ options.minIntervalMs = min_interval;
+ options.preferredAccuracyMeters = preferred_accuracy;
+ options.preferredTimeMs = preferred_time;
+ options.lowPowerMode = low_power_mode;
+ auto status = gnssHalAidl->setPositionMode(options);
return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
}
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 5178132..f8a8168 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -16,20 +16,18 @@
#define LOG_TAG "NetworkStatsNative"
+#include <cutils/qtaguid.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include "core_jni_helpers.h"
#include <jni.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <utils/misc.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <utils/Log.h>
+#include <utils/misc.h>
-#include "android-base/unique_fd.h"
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
@@ -104,10 +102,15 @@
}
}
+static int deleteTagData(JNIEnv* /* env */, jclass /* clazz */, jint uid) {
+ return qtaguid_deleteTagData(0, uid);
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
{"nativeGetUidStat", "(II)J", (void*)getUidStat},
+ {"nativeDeleteTagData", "(I)I", (void*)deleteTagData},
};
int register_android_server_net_NetworkStatsService(JNIEnv* env) {
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index d760b4d..424ffd4 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -41,7 +41,7 @@
jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) {
JNIEnv* env = getJniEnv();
ScopedJniString jniSetId{env, setid_string};
- auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str());
+ auto status = mIAGnssRil->setSetId((IAGnssRil::SetIdType)type, jniSetId.c_str());
return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed.");
}
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index fbdeec6..6c0d5d9 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -359,9 +359,7 @@
jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
jobjectArray gnssAgcArray = nullptr;
- if (data.gnssAgcs.has_value()) {
- gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
- }
+ gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
env->DeleteLocalRef(clock);
@@ -508,8 +506,8 @@
return gnssMeasurementArray;
}
-jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
- JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(JNIEnv* env,
+ const std::vector<GnssAgc>& agcs) {
if (agcs.size() == 0) {
return nullptr;
}
@@ -518,10 +516,7 @@
env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
for (uint16_t i = 0; i < agcs.size(); ++i) {
- if (!agcs[i].has_value()) {
- continue;
- }
- const GnssAgc& gnssAgc = agcs[i].value();
+ const GnssAgc& gnssAgc = agcs[i];
jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 9b346312..17af949 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -62,8 +62,8 @@
jobjectArray translateAllGnssMeasurements(
JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
- jobjectArray translateAllGnssAgcs(
- JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
+ jobjectArray translateAllGnssAgcs(JNIEnv* env,
+ const std::vector<hardware::gnss::GnssData::GnssAgc>& agcs);
void translateAndSetGnssData(const hardware::gnss::GnssData& data);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 733cfcd..3bda7bf0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -56,6 +56,8 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
@@ -66,9 +68,12 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
@@ -3918,8 +3923,8 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller)
- || isPasswordLimitingAdminTargetingP(caller));
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isSystemUid(caller) || isPasswordLimitingAdminTargetingP(caller));
if (parent) {
Preconditions.checkCallAuthorization(
@@ -4772,7 +4777,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller));
Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
return !isSeparateProfileChallengeEnabled(caller.getUserId());
@@ -4860,12 +4866,12 @@
enforceUserUnlocked(caller.getUserId());
if (parent) {
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method on parent.");
} else {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || isDeviceOwner(caller) || isProfileOwner(caller),
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Must have " + REQUEST_PASSWORD_COMPLEXITY
+ " permission, or be a profile owner or device owner.");
}
@@ -4888,7 +4894,8 @@
"Provided complexity is not one of the allowed values.");
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
synchronized (getLockObject()) {
@@ -4968,7 +4975,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller));
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
@@ -5160,7 +5167,7 @@
}
// If caller has PO (or DO) throw or fail silently depending on its target SDK level.
- if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) {
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) {
@@ -5219,7 +5226,7 @@
return false;
}
- boolean callerIsDeviceOwnerAdmin = isDeviceOwner(caller);
+ boolean callerIsDeviceOwnerAdmin = isDefaultDeviceOwner(caller);
boolean doNotAskCredentialsOnBoot =
(flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
@@ -5406,7 +5413,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
// timeoutMs with value 0 means that the admin doesn't participate
// timeoutMs is clamped to the interval in case the internal constants change in the future
final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -5575,7 +5583,8 @@
}
private boolean canManageCaCerts(CallerIdentity caller) {
- return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller)))
+ return (caller.hasAdminComponent() && (isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))
|| hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
}
@@ -5689,7 +5698,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
@@ -5754,7 +5763,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -5818,12 +5827,12 @@
}
private boolean canInstallCertificates(CallerIdentity caller) {
- return isProfileOwner(caller) || isDeviceOwner(caller)
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
}
private boolean canChooseCertificates(CallerIdentity caller) {
- return isProfileOwner(caller) || isDeviceOwner(caller)
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_SELECTION);
}
@@ -5871,7 +5880,7 @@
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_SELECTION)));
final int granteeUid;
@@ -5984,7 +5993,7 @@
// If not, fall back to the device owner check.
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
}
@VisibleForTesting
@@ -6047,7 +6056,7 @@
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -6182,7 +6191,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -6337,14 +6346,15 @@
final int userId = caller.getUserId();
// Ensure calling process is device/profile owner.
if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
} else if (!Collections.disjoint(
scopes, DEVICE_OWNER_OR_ORGANIZATION_OWNED_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
synchronized (getLockObject()) {
@@ -6434,7 +6444,8 @@
// * Either it's a profile owner / device owner, if componentName is provided
// * Or it's an app querying its own delegation scopes
if (caller.hasAdminComponent()) {
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
} else {
Preconditions.checkCallAuthorization(isPackage(caller, delegatePackage),
String.format("Caller with uid %d is not %s", caller.getUid(),
@@ -6467,7 +6478,8 @@
// Retrieve the user ID of the calling process.
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
return getDelegatePackagesInternalLocked(scope, caller.getUserId());
}
@@ -6600,7 +6612,8 @@
final CallerIdentity caller = getCallerIdentity(who);
// Ensure calling process is device/profile owner.
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(caller.getUserId());
@@ -6712,7 +6725,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
if (vpnPackage == null) {
@@ -6792,7 +6806,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
@@ -6818,7 +6833,8 @@
caller = getCallerIdentity();
} else {
caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
return mInjector.binderWithCleanCallingIdentity(
@@ -6841,7 +6857,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
@@ -6946,8 +6963,9 @@
+ "organization-owned device.");
}
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || calledByProfileOwnerOnOrgOwnedDevice,
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || calledByProfileOwnerOnOrgOwnedDevice
+ || isFinancedDeviceOwner(caller),
"Only device owners or profile owners of organization-owned device can set "
+ "WIPE_RESET_PROTECTION_DATA");
}
@@ -7139,8 +7157,8 @@
}
Preconditions.checkNotNull(who, "ComponentName is null");
CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager
.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
@@ -7189,7 +7207,8 @@
UserHandle.getUserId(frpManagementAgentUid));
} else {
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
admin = getProfileOwnerOrDeviceOwnerLocked(caller);
}
}
@@ -7617,7 +7636,7 @@
public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
@@ -7915,7 +7934,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7934,8 +7954,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isProfileOwner(caller)
- || isDeviceOwner(caller)
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
@@ -7955,7 +7974,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7974,8 +7994,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isProfileOwner(caller)
- || isDeviceOwner(caller)
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
@@ -8067,7 +8086,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8091,7 +8110,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -8108,7 +8127,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -8132,7 +8151,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -8161,7 +8180,7 @@
// which could still contain data related to that user. Should we disallow that, e.g. until
// next boot? Might not be needed given that this still requires user consent.
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REQUEST_BUGREPORT);
@@ -8472,7 +8491,8 @@
}
Objects.requireNonNull(packageList, "packageList is null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && isDefaultDeviceOwner(caller))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES);
@@ -8501,7 +8521,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && isDefaultDeviceOwner(caller))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
@@ -8615,8 +8636,8 @@
@Override
public boolean hasDeviceOwner() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || canManageUsers(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || canManageUsers(caller) || isFinancedDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
return mOwners.hasDeviceOwner();
}
@@ -8633,17 +8654,29 @@
}
}
- private boolean isDeviceOwner(CallerIdentity caller) {
+ /**
+ * Returns {@code true} <b>only if</b> the caller is the device owner and the device owner type
+ * is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. {@code false} is returned for the
+ * case where the caller is not the device owner, there is no device owner, or the device owner
+ * type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}.
+ *
+ */
+ private boolean isDefaultDeviceOwner(CallerIdentity caller) {
synchronized (getLockObject()) {
- if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
- return false;
- }
+ return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+ mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+ }
+ }
- if (caller.hasAdminComponent()) {
- return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
- } else {
- return isUidDeviceOwnerLocked(caller.getUid());
- }
+ private boolean isDeviceOwnerLocked(CallerIdentity caller) {
+ if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
+ return false;
+ }
+
+ if (caller.hasAdminComponent()) {
+ return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
+ } else {
+ return isUidDeviceOwnerLocked(caller.getUid());
}
}
@@ -9073,8 +9106,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null));
@@ -9258,7 +9291,8 @@
final CallerIdentity caller = getCallerIdentity(who);
final int userId = caller.getUserId();
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
Preconditions.checkCallingUser(isManagedProfile(userId));
synchronized (getLockObject()) {
@@ -9289,7 +9323,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setUserName(caller.getUserId(), profileName);
@@ -9725,7 +9760,8 @@
}
private void enforceCanCallLockTaskLocked(CallerIdentity caller) {
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userId = caller.getUserId();
if (!canUserUseLockTaskLocked(userId)) {
@@ -9982,7 +10018,8 @@
ComponentName activity) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
@@ -10009,7 +10046,8 @@
public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
@@ -10030,7 +10068,7 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
if (parent) {
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -10070,7 +10108,7 @@
String packageName, Bundle settings) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
@@ -10168,7 +10206,8 @@
public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER);
synchronized (getLockObject()) {
@@ -10193,7 +10232,8 @@
public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
@@ -10242,7 +10282,8 @@
public void clearCrossProfileIntentFilters(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -10382,7 +10423,8 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -10495,7 +10537,8 @@
+ "system input methods when called on the parent instance of an "
+ "organization-owned device");
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
if (packageList != null) {
@@ -10553,7 +10596,8 @@
if (calledOnParentInstance) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
synchronized (getLockObject()) {
@@ -10732,7 +10776,7 @@
// Only allow the system user to use this method
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
"createAndManageUser was called from non-system user");
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
@@ -10974,7 +11018,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER);
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11008,7 +11052,7 @@
public boolean switchUser(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
boolean switched = false;
@@ -11083,7 +11127,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND);
final int userId = userHandle.getIdentifier();
@@ -11119,7 +11163,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER);
final int userId = userHandle.getIdentifier();
@@ -11135,7 +11179,8 @@
public int logoutUser(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOGOUT_USER);
final int callingUserId = caller.getUserId();
@@ -11224,7 +11269,7 @@
public List<UserHandle> getSecondaryUsers(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
@@ -11244,7 +11289,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
@@ -11255,7 +11301,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11306,7 +11352,7 @@
Objects.requireNonNull(packageNames, "array of packages cannot be null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
@@ -11369,7 +11415,7 @@
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
synchronized (getLockObject()) {
@@ -11390,8 +11436,8 @@
public List<String> listPolicyExemptApps() {
CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
- || isProfileOwner(caller));
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return listPolicyExemptAppsUnchecked();
}
@@ -11432,12 +11478,19 @@
final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
getProfileOwnerOrDeviceOwnerLocked(caller), parent);
- if (isDeviceOwner(caller)) {
+ if (isDefaultDeviceOwner(caller)) {
if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
throw new SecurityException("Device owner cannot set user restriction " + key);
}
Preconditions.checkArgument(!parent,
"Cannot use the parent instance in Device Owner mode");
+ } else if (isFinancedDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+ throw new SecurityException("Cannot set user restriction " + key
+ + " when managing a financed device");
+ }
+ Preconditions.checkArgument(!parent,
+ "Cannot use the parent instance in Financed Device Owner mode");
} else {
boolean profileOwnerCanChangeOnItself = !parent
&& UserRestrictionsUtils.canProfileOwnerChange(key, userHandle);
@@ -11546,7 +11599,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
synchronized (getLockObject()) {
@@ -11561,7 +11615,7 @@
boolean hidden, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
List<String> exemptApps = listPolicyExemptAppsUnchecked();
@@ -11607,7 +11661,7 @@
String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
@@ -11643,7 +11697,7 @@
public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
synchronized (getLockObject()) {
@@ -11687,7 +11741,7 @@
public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
int numberOfAppsInstalled = 0;
@@ -11756,7 +11810,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)));
@@ -11872,7 +11926,8 @@
boolean uninstallBlocked) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
final int userId = caller.getUserId();
@@ -11913,7 +11968,8 @@
if (who != null) {
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDeviceOwner(caller));
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller));
}
try {
return mIPackageManager.getBlockUninstallForUser(packageName, userId);
@@ -12087,7 +12143,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12111,7 +12168,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12136,7 +12194,8 @@
// Check can set secondary lockscreen enabled
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
"User %d is not allowed to call setSecondaryLockscreenEnabled",
caller.getUserId());
@@ -12325,6 +12384,7 @@
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
+ enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
setLockTaskFeaturesLocked(userHandle, flags);
}
@@ -12373,6 +12433,24 @@
});
}
+ private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
+ int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | LOCK_TASK_FEATURE_NOTIFICATIONS;
+
+ if (!isFinancedDeviceOwner(caller)) {
+ return;
+ }
+
+ if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) {
+ throw new SecurityException(
+ "Permitted lock task features when managing a financed device: "
+ + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
+ + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
+ + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+ }
+ }
+
@Override
public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
@@ -12413,7 +12491,7 @@
public void setGlobalSetting(ComponentName who, String setting, String value) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -12454,7 +12532,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkStringNotEmpty(setting, "String setting is null or empty");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING);
synchronized (getLockObject()) {
@@ -12476,8 +12555,8 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -12498,8 +12577,8 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -12510,7 +12589,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
UserHandle userHandle = caller.getUserHandle();
if (mIsAutomotive && !locationEnabled) {
@@ -12592,8 +12671,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -12612,8 +12691,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -12633,7 +12712,8 @@
public void setSecureSetting(ComponentName who, String setting, String value) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -12720,7 +12800,8 @@
public void setMasterVolumeMuted(ComponentName who, boolean on) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED);
synchronized (getLockObject()) {
@@ -12737,7 +12818,8 @@
public boolean isMasterVolumeMuted(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
AudioManager audioManager =
@@ -12750,7 +12832,8 @@
public void setUserIcon(ComponentName who, Bitmap icon) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
mInjector.binderWithCleanCallingIdentity(
@@ -12766,7 +12849,8 @@
public boolean setKeyguardDisabled(ComponentName who, boolean disabled) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
final int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -12808,7 +12892,8 @@
@Override
public boolean setStatusBarDisabled(ComponentName who, boolean disabled) {
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -13042,7 +13127,7 @@
@Override
public boolean isActiveDeviceOwner(int uid) {
- return isDeviceOwner(new CallerIdentity(uid, null, null));
+ return isDefaultDeviceOwner(new CallerIdentity(uid, null, null));
}
@Override
@@ -13633,7 +13718,7 @@
synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
if (policy == null) {
@@ -13831,7 +13916,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mOwners.getSystemUpdateInfo();
}
@@ -13840,7 +13926,7 @@
public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
@@ -13882,11 +13968,15 @@
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE);
synchronized (getLockObject()) {
+ if (isFinancedDeviceOwner(caller)) {
+ enforceCanSetPermissionGrantOnFinancedDevice(packageName, permission);
+ }
long ident = mInjector.binderClearCallingIdentity();
try {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13946,12 +14036,23 @@
}
}
+ private void enforceCanSetPermissionGrantOnFinancedDevice(
+ String packageName, String permission) {
+ if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) {
+ throw new SecurityException("Cannot grant " + permission
+ + " when managing a financed device");
+ } else if (!mOwners.getDeviceOwnerPackageName().equals(packageName)) {
+ throw new SecurityException("Cannot grant permission to a package that is not"
+ + " the device owner");
+ }
+ }
+
@Override
public int getPermissionGrantState(ComponentName admin, String callerPackage,
String packageName, String permission) throws RemoteException {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
@@ -14231,7 +14332,7 @@
}
private void checkIsDeviceOwner(CallerIdentity caller) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller), caller.getUid()
+ " is not device owner");
}
@@ -14263,8 +14364,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -14299,7 +14400,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return isManagedProfile(caller.getUserId());
}
@@ -14308,7 +14410,7 @@
public void reboot(ComponentName admin) {
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REBOOT);
mInjector.binderWithCleanCallingIdentity(() -> {
// Make sure there are no ongoing calls on the device.
@@ -14542,7 +14644,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || canManageUsers(caller) || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
return deviceOwnerAdmin == null ? null : deviceOwnerAdmin.organizationName;
@@ -14576,7 +14679,8 @@
Objects.requireNonNull(who);
Objects.requireNonNull(packageNames);
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Admin %s does not own the profile", caller.getComponentName());
if (!mHasFeature) {
@@ -14627,7 +14731,8 @@
return new ArrayList<>();
}
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Admin %s does not own the profile", caller.getComponentName());
synchronized (getLockObject()) {
@@ -14766,7 +14871,8 @@
final Set<String> affiliationIds = new ArraySet<>(ids);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
final int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -14797,7 +14903,8 @@
Objects.requireNonNull(admin);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
return new ArrayList<String>(getUserData(caller.getUserId()).mAffiliationIds);
@@ -14891,7 +14998,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -14928,7 +15035,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -14961,7 +15068,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -15007,7 +15114,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -15246,7 +15353,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
toggleBackupServiceActive(caller.getUserId(), enabled);
}
@@ -15259,7 +15367,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
@@ -15334,7 +15443,8 @@
}
Objects.requireNonNull(admin);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int callingUserId = caller.getUserId();
@@ -15462,7 +15572,7 @@
final boolean isManagedProfileOwner = isProfileOwner(caller)
&& isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller) || isManagedProfileOwner))
+ && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
synchronized (getLockObject()) {
@@ -15622,7 +15732,7 @@
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller)
+ && (isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
|| hasCallingOrSelfPermission(permission.MANAGE_USERS));
@@ -15654,7 +15764,7 @@
final boolean isManagedProfileOwner = isProfileOwner(caller)
&& isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller) || isManagedProfileOwner))
+ && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
if (mOwners.hasDeviceOwner()) {
checkAllUsersAreAffiliatedWithDevice();
@@ -15815,14 +15925,16 @@
@Override
public long getLastSecurityLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || canManageUsers(caller));
return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
}
@Override
public long getLastBugReportRequestTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || canManageUsers(caller));
return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
}
@@ -15830,7 +15942,7 @@
public long getLastNetworkLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
|| canManageUsers(caller));
final int affectedUserId = getNetworkLoggingAffectedUser();
@@ -15846,7 +15958,8 @@
throw new IllegalArgumentException("token must be at least 32-byte long");
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int userHandle = caller.getUserId();
@@ -15870,7 +15983,8 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int userHandle = caller.getUserId();
@@ -15895,7 +16009,8 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
return isResetPasswordTokenActiveForUserLocked(caller.getUserId());
@@ -15920,7 +16035,8 @@
Objects.requireNonNull(token);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(caller.getUserId());
@@ -15945,7 +16061,7 @@
@Override
public boolean isCurrentInputMethodSetByOwner() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method.");
return getUserData(caller.getUserId()).mCurrentInputMethodSet;
@@ -15956,7 +16072,7 @@
final int userId = user.getIdentifier();
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization((userId == caller.getUserId())
- || isProfileOwner(caller) || isDeviceOwner(caller)
+ || isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasFullCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
@@ -15973,7 +16089,8 @@
Objects.requireNonNull(callback, "callback is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA);
long ident = mInjector.binderClearCallingIdentity();
@@ -16005,7 +16122,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED);
synchronized (getLockObject()) {
@@ -16054,7 +16171,8 @@
"Provided administrator and target have the same package name.");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
final int callingUserId = caller.getUserId();
final DevicePolicyData policy = getUserData(callingUserId);
@@ -16096,7 +16214,7 @@
if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
}
- } else if (isDeviceOwner(caller)) {
+ } else if (isDefaultDeviceOwner(caller)) {
ownerType = ADMIN_TYPE_DEVICE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_DEVICE_OWNER);
@@ -16177,7 +16295,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final String startUserSessionMessageString =
startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
@@ -16202,7 +16320,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final String endUserSessionMessageString =
endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
@@ -16227,7 +16345,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16242,7 +16360,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16258,7 +16376,8 @@
@Nullable
public PersistableBundle getTransferOwnershipBundle() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int callingUserId = caller.getUserId();
@@ -16288,7 +16407,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
if (tm != null) {
@@ -16309,7 +16428,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
if (apnId < 0) {
return false;
@@ -16331,7 +16450,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return removeOverrideApnUnchecked(apnId);
}
@@ -16352,7 +16471,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return getOverrideApnsUnchecked();
}
@@ -16373,7 +16492,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED);
setOverrideApnsEnabledUnchecked(enabled);
@@ -16393,7 +16512,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
() -> mContext.getContentResolver().query(
@@ -16476,7 +16595,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
@@ -16515,7 +16634,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final int currentMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
switch (currentMode) {
@@ -16537,7 +16656,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
}
@@ -16547,8 +16666,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
DevicePolicyEventLogger
@@ -16923,7 +17042,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(packages, "packages is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
@@ -16942,7 +17062,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName());
@@ -16954,7 +17075,7 @@
Objects.requireNonNull(who, "Admin component name must be provided");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Common Criteria mode can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
synchronized (getLockObject()) {
@@ -16974,7 +17095,7 @@
if (who != null) {
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Common Criteria mode can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -17446,7 +17567,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller)
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
synchronized (getLockObject()) {
@@ -17467,7 +17588,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
// Only the DPC can set this ID.
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Only a Device Owner or Profile Owner may set the Enterprise ID.");
// Empty enterprise ID must not be provided in calls to this method.
Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
@@ -18141,27 +18262,51 @@
@DeviceOwnerType int deviceOwnerType) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- verifyDeviceOwnerTypePreconditions(admin);
- final String packageName = admin.getPackageName();
+ synchronized (getLockObject()) {
+ setDeviceOwnerTypeLocked(admin, deviceOwnerType);
+ }
+ }
+
+ private void setDeviceOwnerTypeLocked(ComponentName admin,
+ @DeviceOwnerType int deviceOwnerType) {
+ String packageName = admin.getPackageName();
+
+ verifyDeviceOwnerTypePreconditionsLocked(admin);
Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
"The device owner type has already been set for " + packageName);
- synchronized (getLockObject()) {
- mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
- }
+ mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
}
@Override
@DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) {
- verifyDeviceOwnerTypePreconditions(admin);
synchronized (getLockObject()) {
- return mOwners.getDeviceOwnerType(admin.getPackageName());
+ verifyDeviceOwnerTypePreconditionsLocked(admin);
+ return getDeviceOwnerTypeLocked(admin.getPackageName());
}
}
- private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) {
+ @DeviceOwnerType
+ private int getDeviceOwnerTypeLocked(String packageName) {
+ return mOwners.getDeviceOwnerType(packageName);
+ }
+
+ /**
+ * {@code true} is returned <b>only if</b> the caller is the device owner and the device owner
+ * type is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. {@code false} is returned for
+ * the case where the caller is not the device owner, there is no device owner, or the device
+ * owner type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+ */
+ private boolean isFinancedDeviceOwner(CallerIdentity caller) {
+ synchronized (getLockObject()) {
+ return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+ mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_FINANCED;
+ }
+ }
+
+ private void verifyDeviceOwnerTypePreconditionsLocked(@NonNull ComponentName admin) {
Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner");
Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin),
"admin is not the device owner");
@@ -18172,7 +18317,7 @@
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
Preconditions.checkState(canUsbDataSignalingBeDisabled(),
@@ -18213,7 +18358,7 @@
synchronized (getLockObject()) {
// If the caller is an admin, return the policy set by itself. Otherwise
// return the device-wide policy.
- if (isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
return getProfileOwnerOrDeviceOwnerLocked(caller).mUsbDataSignalingEnabled;
} else {
return isUsbDataSignalingEnabledInternalLocked();
@@ -18254,7 +18399,7 @@
public void setMinimumRequiredWifiSecurityLevel(int level) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Wi-Fi minimum security level can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18284,7 +18429,7 @@
public void setSsidAllowlist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID allowlist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18306,7 +18451,7 @@
public List<String> getSsidAllowlist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
|| isSystemUid(caller),
"SSID allowlist can only be retrieved by a device owner or "
+ "a profile owner on an organization-owned device or a system app.");
@@ -18322,7 +18467,7 @@
public void setSsidDenylist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID denylist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18344,7 +18489,7 @@
public List<String> getSsidDenylist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
|| isSystemUid(caller),
"SSID denylist can only be retrieved by a device owner or "
+ "a profile owner on an organization-owned device or a system app.");
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ff1b6f6..83fa7ac 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -17,17 +17,23 @@
package com.android.server.companion.virtual;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInputConstants;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.DisplayInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -46,18 +52,31 @@
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
+ private DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
private InputController.NativeWrapper mNativeWrapperMock;
+ @Mock
+ private IInputManager mIInputManagerMock;
private InputController mInputController;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = "uniqueId";
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+ InputManager.resetInstance(mIInputManagerMock);
+ doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
mInputController = new InputController(new Object(), mNativeWrapperMock);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e36263e..ceb723a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.DisplayInfo;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
@@ -80,6 +82,8 @@
private static final int DISPLAY_ID = 2;
private static final int PRODUCT_ID = 10;
private static final int VENDOR_ID = 5;
+ private static final String UNIQUE_ID = "uniqueid";
+ private static final String PHYS = "phys";
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
@@ -116,6 +120,12 @@
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = UNIQUE_ID;
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -274,7 +284,8 @@
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString());
}
@Test
@@ -284,7 +295,8 @@
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+ anyString());
}
@Test
@@ -294,8 +306,8 @@
BINDER, new Point(WIDTH, HEIGHT));
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
- WIDTH);
+ verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@Test
@@ -315,7 +327,7 @@
final int action = VirtualKeyEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
.setAction(action).build());
verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -340,7 +352,7 @@
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -355,7 +367,7 @@
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -381,7 +393,7 @@
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x).setRelativeY(y).build());
@@ -395,7 +407,7 @@
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -422,7 +434,7 @@
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
@@ -437,7 +449,7 @@
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -470,7 +482,7 @@
final int action = VirtualTouchEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -489,7 +501,7 @@
final float majorAxisSize = 10.0f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
.setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 564c4e4..64ce6b2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -28,6 +28,14 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -101,6 +109,7 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -138,6 +147,9 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
+import com.android.server.pm.RestrictionsSet;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserRestrictionsUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -7764,6 +7776,296 @@
}
@Test
+ public void testSetUserRestriction_financeDo_invalidRestrictions_restrictionNotSet()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+ assertNoDeviceOwnerRestrictions();
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.addUserRestriction(admin1, restriction));
+
+ verify(getServices().userManagerInternal, never())
+ .setDevicePolicyUserRestrictions(anyInt(), any(), any(), anyBoolean());
+ }
+ }
+ }
+
+ @Test
+ public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+ if (UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+ assertNoDeviceOwnerRestrictions();
+ dpm.addUserRestriction(admin1, restriction);
+
+ Bundle globalRestrictions =
+ dpms.getDeviceOwnerAdminLocked().getGlobalUserRestrictions(
+ UserManagerInternal.OWNER_TYPE_DEVICE_OWNER);
+ RestrictionsSet localRestrictions = new RestrictionsSet();
+ localRestrictions.updateRestrictions(
+ UserHandle.USER_SYSTEM,
+ dpms.getDeviceOwnerAdminLocked().getLocalUserRestrictions(
+ UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
+ verify(getServices().userManagerInternal)
+ .setDevicePolicyUserRestrictions(eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(globalRestrictions),
+ MockUtils.checkUserRestrictions(
+ UserHandle.USER_SYSTEM, localRestrictions),
+ eq(true));
+ reset(getServices().userManagerInternal);
+
+ dpm.clearUserRestriction(admin1, restriction);
+ reset(getServices().userManagerInternal);
+ }
+ }
+ }
+
+ @Test
+ public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet()
+ throws Exception {
+ int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setLockTaskFeatures(admin1, validLockTaskFeatures);
+
+ verify(getServices().iactivityTaskManager)
+ .updateLockTaskFeatures(eq(UserHandle.USER_SYSTEM), eq(validLockTaskFeatures));
+ }
+
+ @Test
+ public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
+ throws Exception {
+ int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ // Called during setup.
+ verify(getServices().iactivityTaskManager).updateLockTaskFeatures(anyInt(), anyInt());
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setLockTaskFeatures(admin1, invalidLockTaskFeatures));
+
+ verifyNoMoreInteractions(getServices().iactivityTaskManager);
+ }
+
+ @Test
+ public void testIsUninstallBlocked_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().ipackageManager.getBlockUninstallForUser(
+ eq(packageName), eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+
+ assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue();
+ }
+
+ @Test
+ public void testSetUninstallBlocked_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setUninstallBlocked(admin1, packageName, false);
+
+ verify(getServices().ipackageManager)
+ .setBlockUninstallForUser(eq(packageName), eq(false),
+ eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
+ List<String> packages = new ArrayList<>();
+ packages.add("com.android.foo.package");
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setUserControlDisabledPackages(admin1, packages);
+
+ verify(getServices().packageManagerInternal)
+ .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages));
+ }
+
+ @Test
+ public void testGetUserControlDisabledPackages_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.getUserControlDisabledPackages(admin1)).isEmpty();
+ }
+
+ @Test
+ public void testSetOrganizationName_financeDo_success() throws Exception {
+ String organizationName = "Test Organization";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setOrganizationName(admin1, organizationName);
+
+ assertThat(dpm.getDeviceOwnerOrganizationName()).isEqualTo(organizationName);
+ }
+
+ @Test
+ public void testSetShortSupportMessage_financeDo_success() throws Exception {
+ String supportMessage = "Test short support message";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setShortSupportMessage(admin1, supportMessage);
+
+ assertThat(dpm.getShortSupportMessage(admin1)).isEqualTo(supportMessage);
+ }
+
+ @Test
+ public void testIsBackupServiceEnabled_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().ibackupManager.isBackupServiceActive(eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+
+ assertThat(dpm.isBackupServiceEnabled(admin1)).isTrue();
+ }
+
+ @Test
+ public void testSetBackupServiceEnabled_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setBackupServiceEnabled(admin1, true);
+
+ verify(getServices().ibackupManager)
+ .setBackupServiceActive(eq(UserHandle.USER_SYSTEM), eq(true));
+ }
+
+ @Test
+ public void testIsLockTaskPermitted_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ mockPolicyExemptApps(packageName);
+ mockVendorPolicyExemptApps();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.isLockTaskPermitted(packageName)).isTrue();
+ }
+
+ @Test
+ public void testSetLockTaskPackages_financeDo_success() throws Exception {
+ String[] packages = {"com.android.foo.package"};
+ mockEmptyPolicyExemptApps();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setLockTaskPackages(admin1, packages);
+
+ verify(getServices().iactivityManager)
+ .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(packages));
+ }
+
+ @Test
+ public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
+ IntentFilter filter = new IntentFilter();
+ ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.addPersistentPreferredActivity(admin1, filter, target);
+
+ verify(getServices().ipackageManager)
+ .addPersistentPreferredActivity(eq(filter), eq(target), eq(UserHandle.USER_SYSTEM));
+ verify(getServices().ipackageManager)
+ .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
+ String packageName = admin2.getPackageName();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.clearPackagePersistentPreferredActivities(admin1, packageName);
+
+ verify(getServices().ipackageManager)
+ .clearPackagePersistentPreferredActivities(
+ eq(packageName), eq(UserHandle.USER_SYSTEM));
+ verify(getServices().ipackageManager)
+ .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testWipeData_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ when(mMockContext.getResources()
+ .getString(R.string.work_profile_deleted_description_dpm_wipe))
+ .thenReturn("Test string");
+
+ dpm.wipeData(0);
+
+ verifyRebootWipeUserData(/* wipeEuicc= */ false);
+ }
+
+ @Test
+ public void testIsDeviceOwnerApp_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.isDeviceOwnerApp(admin1.getPackageName())).isTrue();
+ }
+
+ @Test
+ public void testClearDeviceOwnerApp_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+ assertThat(dpm.getDeviceOwnerComponentOnAnyUser()).isNull();
+ assertThat(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)).isFalse();
+ verify(mMockContext.spiedContext, times(2))
+ .sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+ eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void testSetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPermissionGrantState(admin1, admin1.getPackageName(),
+ permission.READ_CALENDAR,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+ }
+
+ @Test
+ public void testSetPermissionGrantState_financeDo_grantPermissionToNonDeviceOwnerPackage_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPermissionGrantState(admin1, "com.android.foo.package",
+ permission.READ_PHONE_STATE,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+ }
+
+ @Test
public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
assertThrows(SecurityException.class,
() -> dpm.setUsbDataSignalingEnabled(true));
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 15f3ed1..4cb46b4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -109,6 +109,14 @@
public static Bundle checkUserRestrictions(String... keys) {
final Bundle expected = DpmTestUtils.newRestrictions(
java.util.Objects.requireNonNull(keys));
+ return checkUserRestrictions(expected);
+ }
+
+ public static Bundle checkUserRestrictions(Bundle expected) {
+ return createUserRestrictionsBundleMatcher(expected);
+ }
+
+ private static Bundle createUserRestrictionsBundleMatcher(Bundle expected) {
final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
@Override
public boolean matches(Object item) {
@@ -129,6 +137,15 @@
public static RestrictionsSet checkUserRestrictions(int userId, String... keys) {
final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId,
java.util.Objects.requireNonNull(keys));
+ return checkUserRestrictions(userId, expected);
+ }
+
+ public static RestrictionsSet checkUserRestrictions(int userId, RestrictionsSet expected) {
+ return createUserRestrictionsSetMatcher(userId, expected);
+ }
+
+ private static RestrictionsSet createUserRestrictionsSetMatcher(
+ int userId, RestrictionsSet expected) {
final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() {
@Override
public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
new file mode 100644
index 0000000..4ae9613
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2021 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.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link com.android.server.power.LowPowerStandbyController}.
+ *
+ * Build/Install/Run:
+ * atest LowPowerStandbyControllerTest
+ */
+public class LowPowerStandbyControllerTest {
+ private static final int STANDBY_TIMEOUT = 5000;
+
+ private LowPowerStandbyController mController;
+ private BroadcastInterceptingContext mContextSpy;
+ private Resources mResourcesSpy;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+
+ @Mock
+ private AlarmManager mAlarmManagerMock;
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private PowerManagerInternal mPowerManagerInternalMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
+ when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
+ PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+ mResourcesSpy = spy(mContextSpy.getResources());
+ when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbySupported))
+ .thenReturn(true);
+ when(mResourcesSpy.getInteger(
+ com.android.internal.R.integer.config_lowPowerStandbyNonInteractiveTimeout))
+ .thenReturn(STANDBY_TIMEOUT);
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbyEnabledByDefault))
+ .thenReturn(false);
+
+ FakeSettingsProvider.clearSettingsProvider();
+ MockContentResolver cr = new MockContentResolver(mContextSpy);
+ cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+
+ mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(),
+ () -> mClock.now());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
+ LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
+ }
+
+ @Test
+ public void testOnSystemReady_isInactivate() {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ @Test
+ public void testActivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+ }
+
+ private void awaitStandbyTimeoutAlarm() {
+ ArgumentCaptor<Long> timeArg = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<AlarmManager.OnAlarmListener> listenerArg =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ verify(mAlarmManagerMock).setExact(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ timeArg.capture(), anyString(),
+ listenerArg.capture(), any());
+ mClock.reset();
+ mClock.fastForward(timeArg.getValue());
+ listenerArg.getValue().onAlarm();
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void testOnNonInteractive_notImmediatelyActive() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+
+ setNonInteractive();
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ @Test
+ public void testOnNonInteractive_activateAfterStandbyTimeout() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+
+ setNonInteractive();
+ awaitStandbyTimeoutAlarm();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+ }
+
+ @Test
+ public void testOnNonInteractive_doesNotActivateWhenBecomingInteractive() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+
+ setNonInteractive();
+ advanceTime(STANDBY_TIMEOUT / 2);
+ setInteractive();
+ verifyStandbyAlarmCancelled();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ private void verifyStandbyAlarmCancelled() {
+ InOrder inOrder = inOrder(mAlarmManagerMock);
+ inOrder.verify(mAlarmManagerMock, atLeast(0)).setExact(anyInt(), anyLong(), anyString(),
+ any(), any());
+ inOrder.verify(mAlarmManagerMock).cancel((AlarmManager.OnAlarmListener) any());
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testOnInteractive_deactivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setInteractive();
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+ }
+
+ @Test
+ public void testOnDozeMaintenance_deactivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ mController.setActiveDuringMaintenance(false);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setDeviceIdleMode(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+ }
+
+ @Test
+ public void testOnDozeMaintenance_activeDuringMaintenance_staysActive() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ mController.setActiveDuringMaintenance(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setDeviceIdleMode(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false);
+ }
+
+ @Test
+ public void testOnDozeMaintenanceEnds_activate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setDeviceIdleMode(false);
+ advanceTime(1000);
+ setDeviceIdleMode(true);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+ }
+
+ @Test
+ public void testLowPowerStandbyDisabled_doesNotActivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(false);
+ setNonInteractive();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any());
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ @Test
+ public void testLowPowerStandbyEnabled_EnabledChangedBroadcastsAreSent() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ mController.setEnabled(false);
+ futureIntent.assertNotReceived();
+
+ futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ mController.setEnabled(true);
+ assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+
+ futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ mController.setEnabled(true);
+ futureIntent.assertNotReceived();
+
+ futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+
+ mController.setEnabled(false);
+ assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+ }
+
+ @Test
+ public void testSetEnabled_WhenNotSupported_DoesNotEnable() throws Exception {
+ setLowPowerStandbySupportedConfig(false);
+ mController.systemReady();
+
+ mController.setEnabled(true);
+
+ assertThat(mController.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void testIsSupported_WhenSupported() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ assertThat(mController.isSupported()).isTrue();
+ }
+
+ @Test
+ public void testIsSupported_WhenNotSupported() throws Exception {
+ setLowPowerStandbySupportedConfig(false);
+ mController.systemReady();
+
+ assertThat(mController.isSupported()).isFalse();
+ }
+
+ @Test
+ public void testAllowlistChange_servicesAreNotified() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ LowPowerStandbyControllerInternal service = LocalServices.getService(
+ LowPowerStandbyControllerInternal.class);
+ service.addToAllowlist(10);
+ mTestLooper.dispatchAll();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10});
+
+ service.removeFromAllowlist(10);
+ mTestLooper.dispatchAll();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {});
+ }
+
+ @Test
+ public void testForceActive() throws Exception {
+ setLowPowerStandbySupportedConfig(false);
+ mController.systemReady();
+
+ mController.forceActive(true);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true);
+
+ mController.forceActive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false);
+ }
+
+ private void setLowPowerStandbySupportedConfig(boolean supported) {
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbySupported))
+ .thenReturn(supported);
+ }
+
+ private void setInteractive() throws Exception {
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+ }
+
+ private void setNonInteractive() throws Exception {
+ when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+ mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+ }
+
+ private void setDeviceIdleMode(boolean idle) throws Exception {
+ when(mIPowerManagerMock.isDeviceIdleMode()).thenReturn(idle);
+ mContextSpy.sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index d35c679..51dbd97 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.power;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -65,6 +67,7 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerSaveState;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
@@ -86,6 +89,7 @@
import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
+import com.android.server.power.PowerManagerService.WakeLock;
import com.android.server.power.batterysaver.BatterySaverController;
import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
@@ -283,6 +287,13 @@
void invalidateIsInteractiveCaches() {
// Avoids an SELinux failure.
}
+
+ @Override
+ LowPowerStandbyController createLowPowerStandbyController(Context context,
+ Looper looper) {
+ return new LowPowerStandbyController(context, mTestLooper.getLooper(),
+ SystemClock::elapsedRealtime);
+ }
});
return mService;
}
@@ -293,6 +304,7 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.removeServiceForTest(BatteryManagerInternal.class);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
FakeSettingsProvider.clearSettingsProvider();
}
@@ -1575,4 +1587,60 @@
.setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue();
verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig));
}
+
+ @Test
+ public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.setDeviceIdleModeInternal(true);
+
+ assertThat(wakeLock.mDisabled).isFalse();
+ }
+
+ @Test
+ public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.setDeviceIdleModeInternal(true);
+ mService.setLowPowerStandbyActiveInternal(true);
+
+ assertThat(wakeLock.mDisabled).isTrue();
+ }
+
+ @Test
+ public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.setDeviceIdleModeInternal(true);
+ mService.setLowPowerStandbyActiveInternal(true);
+ mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid});
+
+ assertThat(wakeLock.mDisabled).isFalse();
+ }
+
+ @Test
+ public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
+ mService.setDeviceIdleModeInternal(true);
+ mService.setLowPowerStandbyActiveInternal(true);
+
+ assertThat(wakeLock.mDisabled).isFalse();
+ }
+
+ private WakeLock acquireWakeLock(String tag, int flags) {
+ IBinder token = new Binder();
+ String packageName = "pkg.name";
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ return mService.findWakeLockLocked(token);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
index 09612e3..a73fcb8 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
@@ -211,6 +211,25 @@
dumpLog(log, false));
}
+ @Test
+ public void testAddSystemWakelock() {
+ final int tagDatabaseSize = 6;
+ final int logSize = 10;
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ WakeLockLog log = new WakeLockLog(injectorSpy);
+
+ when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+ log.onWakeLockAcquired("TagPartial", 101,
+ PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
+
+ assertEquals("Wake Lock Log\n"
+ + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,system-wakelock)\n"
+ + " -\n"
+ + " Events: 1, Time-Resets: 0\n"
+ + " Buffer, Bytes used: 3\n",
+ dumpLog(log, false));
+ }
+
private String dumpLog(WakeLockLog log, boolean includeTagDb) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 5eed30b..91d4f8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -67,6 +67,7 @@
@Mock Vibrator mVibrator;
private final String callPkg = "com.android.server.notification";
+ private final String sysPkg = "android";
private final int callUid = 10;
private String smsPkg;
private final int smsUid = 11;
@@ -79,6 +80,7 @@
private NotificationRecord mRecordHighCall;
private NotificationRecord mRecordHighCallStyle;
private NotificationRecord mRecordEmail;
+ private NotificationRecord mRecordSystemMax;
private NotificationRecord mRecordInlineReply;
private NotificationRecord mRecordSms;
private NotificationRecord mRecordStarredContact;
@@ -191,6 +193,12 @@
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
+ Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
+ mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg,
+ sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId),
+ "", 1244), getDefaultChannel());
+ mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
+
Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
@@ -267,6 +275,7 @@
}
expected.add(mRecordStarredContact);
expected.add(mRecordContact);
+ expected.add(mRecordSystemMax);
expected.add(mRecordEmail);
expected.add(mRecordUrgent);
expected.add(mNoMediaSessionMedia);
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index 24fda17..e655013 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -69,6 +69,12 @@
private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats =
new SparseArray<>();
+ private AppStandbyInternal mAppStandby;
+
+ BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
+ mAppStandby = appStandby;
+ }
+
// TODO (206518114): Move all callbacks handling to a handler thread.
void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
UserHandle targetUser, long idForResponseEvent,
@@ -132,8 +138,7 @@
if (dispatchTimestampMs >= timestampMs) {
continue;
}
- // TODO (206518114): Make the constant configurable.
- if (elapsedDurationMs <= 2 * 60 * 1000) {
+ if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) {
final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
final BroadcastResponseStats responseStats =
getBroadcastResponseStats(broadcastEvent);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e90d28a..6906f20 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -281,7 +281,7 @@
mHandler = new H(BackgroundThread.get().getLooper());
mAppStandby = mInjector.getAppStandbyController(getContext());
- mResponseStatsTracker = new BroadcastResponseStatsTracker();
+ mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby);
mAppTimeLimit = new AppTimeLimitController(getContext(),
new AppTimeLimitController.TimeLimitCallbackListener() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 90ccec8..a061618 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -69,6 +69,7 @@
import com.android.server.LocalServices;
import com.android.server.am.AssistDataRequester;
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+import com.android.server.power.LowPowerStandbyControllerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.ActivityAssistInfo;
@@ -89,6 +90,11 @@
static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
System.getProperty("vendor.powerhal.interaction.max", "200"));
static final int BOOST_TIMEOUT_MS = 300;
+ /**
+ * The maximum time an app can stay on the Low Power Standby allowlist when
+ * the session is shown. There to safeguard against apps that don't call hide.
+ */
+ private static final int LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS = 120_000;
// TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it
// in the future.
static final int MAX_POWER_BOOST_TIMEOUT = 10_000;
@@ -124,6 +130,10 @@
Executors.newSingleThreadScheduledExecutor();
private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
private final PowerManagerInternal mPowerManagerInternal;
+ private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
+ private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
+ this::removeFromLowPowerStandbyAllowlist;
+ private boolean mLowPowerStandbyAllowlisted;
private PowerBoostSetter mSetPowerBoostRunnable;
private final Handler mFgHandler;
@@ -211,6 +221,8 @@
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mLowPowerStandbyControllerInternal = LocalServices.getService(
+ LowPowerStandbyControllerInternal.class);
mAppOps = context.getSystemService(AppOpsManager.class);
mFgHandler = FgThread.getHandler();
mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager,
@@ -322,6 +334,15 @@
mSetPowerBoostRunnable = new PowerBoostSetter(
Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT));
mFgHandler.post(mSetPowerBoostRunnable);
+
+ if (mLowPowerStandbyControllerInternal != null) {
+ mLowPowerStandbyControllerInternal.addToAllowlist(mCallingUid);
+ mLowPowerStandbyAllowlisted = true;
+ mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable);
+ mFgHandler.postDelayed(mRemoveFromLowPowerStandbyAllowlistRunnable,
+ LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS);
+ }
+
mCallback.onSessionShown(this);
return true;
}
@@ -493,6 +514,9 @@
}
// A negative value indicates canceling previous boost.
mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
+ if (mLowPowerStandbyControllerInternal != null) {
+ removeFromLowPowerStandbyAllowlist();
+ }
mCallback.onSessionHidden(this);
}
if (mFullyBound) {
@@ -730,6 +754,16 @@
}
}
+ private void removeFromLowPowerStandbyAllowlist() {
+ synchronized (mLock) {
+ if (mLowPowerStandbyAllowlisted) {
+ mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable);
+ mLowPowerStandbyControllerInternal.removeFromAllowlist(mCallingUid);
+ mLowPowerStandbyAllowlisted = false;
+ }
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 4e98f42..d4fa1dd 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -24,14 +24,21 @@
java_test_host {
name: "ApkVerityTest",
srcs: ["src/**/*.java"],
- libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+ libs: [
+ "tradefed",
+ "compatibility-tradefed",
+ "compatibility-host-util",
+ ],
static_libs: [
"block_device_writer_jar",
"frameworks-base-hostutils",
],
- test_suites: ["general-tests", "vts"],
- target_required: [
- "block_device_writer_module",
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ data_device_bins: [
+ "block_device_writer",
],
data: [
":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 0b5f0f6..e5d009d 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,12 +24,7 @@
}
cc_test {
- // Depending on how the test runs, the executable may be uploaded to different location.
- // Before the bug in the file pusher is fixed, workaround by making the name unique.
- // See b/124718249#comment12.
- name: "block_device_writer_module",
- stem: "block_device_writer",
-
+ name: "block_device_writer",
srcs: ["block_device_writer.cpp"],
cflags: [
"-D_FILE_OFFSET_BITS=64",
@@ -38,31 +33,25 @@
"-Wextra",
"-g",
],
- shared_libs: ["libbase", "libutils"],
- // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
- // the uploader does not pick up the executable from correct output location. The following
- // workaround allows the test to:
- // * upload the 32-bit exectuable for both 32 and 64 bits devices to use
- // * refer to the same executable name in Java
- // * no need to force the Java test to be archiecture specific.
- //
- // See b/145573317 for details.
- multilib: {
- lib32: {
- suffix: "",
- },
- lib64: {
- suffix: "64", // not really used
- },
- },
+ shared_libs: [
+ "libbase",
+ "libutils",
+ ],
+ compile_multilib: "first",
auto_gen_config: false,
- test_suites: ["general-tests", "pts", "vts"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
gtest: false,
}
java_library_host {
name: "block_device_writer_jar",
srcs: ["src/**/*.java"],
- libs: ["tradefed", "junit"],
+ libs: [
+ "tradefed",
+ "junit",
+ ],
}
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 5c2c15b..730daf3 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,7 +32,7 @@
* <p>To use this class, please push block_device_writer binary to /data/local/tmp.
* 1. In Android.bp, add:
* <pre>
- * target_required: ["block_device_writer_module"],
+ * data_device_bins: ["block_device_writer"],
* </pre>
* 2. In AndroidText.xml, add:
* <pre>